@@ -9,6 +9,10 @@ import {
9
9
GIST_BACKUP_FILE_NAME ,
10
10
GIST_BACKUP_KEY ,
11
11
SETTINGS_KEY ,
12
+ TOKENS_KEY ,
13
+ FILES_KEY ,
14
+ COLLECTIONS_KEY ,
15
+ SUBS_KEY ,
12
16
} from '@/constants' ;
13
17
import { InternalServerError , RequestInvalidError } from '@/restful/errors' ;
14
18
import Gist from '@/utils/gist' ;
@@ -20,36 +24,7 @@ export default function register($app) {
20
24
$app . get ( '/api/utils/env' , getEnv ) ; // get runtime environment
21
25
$app . get ( '/api/utils/backup' , gistBackup ) ; // gist backup actions
22
26
$app . get ( '/api/utils/refresh' , refresh ) ;
23
- $app . post ( '/api/jwt' , ( req , res ) => {
24
- if ( ! ENV ( ) . isNode ) {
25
- return failed (
26
- res ,
27
- new RequestInvalidError (
28
- 'INVALID_ENV' ,
29
- `This endpoint is only available in Node.js environment` ,
30
- ) ,
31
- ) ;
32
- }
33
- try {
34
- const { payload, options } = req . body ;
35
- const jwt = eval ( `require("jsonwebtoken")` ) ;
36
- const secret = eval ( 'process.env.SUB_STORE_FRONTEND_BACKEND_PATH' ) ;
37
- const token = jwt . sign ( payload , secret , options ) ;
38
- return success ( res , {
39
- token,
40
- secret,
41
- } ) ;
42
- } catch ( e ) {
43
- return failed (
44
- res ,
45
- new InternalServerError (
46
- 'JWT_SIGN_FAILED' ,
47
- `Failed to sign JWT token` ,
48
- `Reason: ${ e . message ?? e } ` ,
49
- ) ,
50
- ) ;
51
- }
52
- } ) ;
27
+ $app . post ( '/api/token' , signToken ) ;
53
28
54
29
// Storage management
55
30
$app . route ( '/api/storage' )
@@ -95,9 +70,152 @@ export default function register($app) {
95
70
}
96
71
97
72
function getEnv ( req , res ) {
73
+ if ( req . query . share ) {
74
+ env . feature . share = true ;
75
+ }
98
76
success ( res , env ) ;
99
77
}
100
78
79
+ async function signToken ( req , res ) {
80
+ if ( ! ENV ( ) . isNode ) {
81
+ return failed (
82
+ res ,
83
+ new RequestInvalidError (
84
+ 'INVALID_ENV' ,
85
+ `This endpoint is only available in Node.js environment` ,
86
+ ) ,
87
+ ) ;
88
+ }
89
+ try {
90
+ const { payload, options } = req . body ;
91
+ const ms = eval ( `require("ms")` ) ;
92
+ let token = payload ?. token ;
93
+ if ( token != null ) {
94
+ if ( typeof token !== 'string' || token . length < 1 ) {
95
+ return failed (
96
+ res ,
97
+ new RequestInvalidError (
98
+ 'INVALID_CUSTOM_TOKEN' ,
99
+ `Invalid custom token: ${ token } ` ,
100
+ ) ,
101
+ ) ;
102
+ }
103
+ const tokens = $ . read ( TOKENS_KEY ) || [ ] ;
104
+ if ( tokens . find ( ( t ) => t . token === token ) ) {
105
+ return failed (
106
+ res ,
107
+ new RequestInvalidError (
108
+ 'DUPLICATE_TOKEN' ,
109
+ `Token ${ token } already exists` ,
110
+ ) ,
111
+ ) ;
112
+ }
113
+ }
114
+ const type = payload ?. type ;
115
+ const name = payload ?. name ;
116
+ if ( ! type || ! name )
117
+ return failed (
118
+ res ,
119
+ new RequestInvalidError (
120
+ 'INVALID_PAYLOAD' ,
121
+ `payload type and name are required` ,
122
+ ) ,
123
+ ) ;
124
+ if ( type === 'col' ) {
125
+ const collections = $ . read ( COLLECTIONS_KEY ) || [ ] ;
126
+ const collection = collections . find ( ( c ) => c . name === name ) ;
127
+ if ( ! collection )
128
+ return failed (
129
+ res ,
130
+ new RequestInvalidError (
131
+ 'INVALID_COLLECTION' ,
132
+ `collection ${ name } not found` ,
133
+ ) ,
134
+ ) ;
135
+ } else if ( type === 'file' ) {
136
+ const files = $ . read ( FILES_KEY ) || [ ] ;
137
+ const file = files . find ( ( f ) => f . name === name ) ;
138
+ if ( ! file )
139
+ return failed (
140
+ res ,
141
+ new RequestInvalidError (
142
+ 'INVALID_FILE' ,
143
+ `file ${ name } not found` ,
144
+ ) ,
145
+ ) ;
146
+ } else if ( type === 'sub' ) {
147
+ const subs = $ . read ( SUBS_KEY ) || [ ] ;
148
+ const sub = subs . find ( ( s ) => s . name === name ) ;
149
+ if ( ! sub )
150
+ return failed (
151
+ res ,
152
+ new RequestInvalidError (
153
+ 'INVALID_SUB' ,
154
+ `sub ${ name } not found` ,
155
+ ) ,
156
+ ) ;
157
+ } else {
158
+ return failed (
159
+ res ,
160
+ new RequestInvalidError (
161
+ 'INVALID_TYPE' ,
162
+ `type ${ name } not supported` ,
163
+ ) ,
164
+ ) ;
165
+ }
166
+ let expiresIn = options ?. expiresIn ;
167
+ if ( options ?. expiresIn != null ) {
168
+ expiresIn = ms ( options . expiresIn ) ;
169
+ if ( expiresIn == null || isNaN ( expiresIn ) || expiresIn <= 0 ) {
170
+ return failed (
171
+ res ,
172
+ new RequestInvalidError (
173
+ 'INVALID_EXPIRES_IN' ,
174
+ `Invalid expiresIn option: ${ options . expiresIn } ` ,
175
+ ) ,
176
+ ) ;
177
+ }
178
+ }
179
+ const secret = eval ( 'process.env.SUB_STORE_FRONTEND_BACKEND_PATH' ) ;
180
+ const nanoid = eval ( `require("nanoid")` ) ;
181
+ const tokens = $ . read ( TOKENS_KEY ) || [ ] ;
182
+ // const now = Date.now();
183
+ // for (const key in tokens) {
184
+ // const token = tokens[key];
185
+ // if (token.exp != null || token.exp < now) {
186
+ // delete tokens[key];
187
+ // }
188
+ // }
189
+ if ( ! token ) {
190
+ do {
191
+ token = nanoid . customAlphabet ( nanoid . urlAlphabet ) ( ) ;
192
+ } while ( tokens . find ( ( t ) => t . token === token ) ) ;
193
+ }
194
+
195
+ tokens . push ( {
196
+ ...payload ,
197
+ token,
198
+ createdAt : Date . now ( ) ,
199
+ expiresIn : expiresIn > 0 ? ms ( expiresIn ) : undefined ,
200
+ exp : expiresIn > 0 ? Date . now ( ) + expiresIn : undefined ,
201
+ } ) ;
202
+
203
+ $ . write ( tokens , TOKENS_KEY ) ;
204
+ return success ( res , {
205
+ token,
206
+ secret,
207
+ } ) ;
208
+ } catch ( e ) {
209
+ return failed (
210
+ res ,
211
+ new InternalServerError (
212
+ 'TOKEN_SIGN_FAILED' ,
213
+ `Failed to sign token` ,
214
+ `Reason: ${ e . message ?? e } ` ,
215
+ ) ,
216
+ ) ;
217
+ }
218
+ }
101
219
async function refresh ( _ , res ) {
102
220
// 1. get GitHub avatar and artifact store
103
221
await updateAvatar ( ) ;
0 commit comments