@@ -9,6 +9,10 @@ import {
99    GIST_BACKUP_FILE_NAME , 
1010    GIST_BACKUP_KEY , 
1111    SETTINGS_KEY , 
12+     TOKENS_KEY , 
13+     FILES_KEY , 
14+     COLLECTIONS_KEY , 
15+     SUBS_KEY , 
1216}  from  '@/constants' ; 
1317import  {  InternalServerError ,  RequestInvalidError  }  from  '@/restful/errors' ; 
1418import  Gist  from  '@/utils/gist' ; 
@@ -20,36 +24,7 @@ export default function register($app) {
2024    $app . get ( '/api/utils/env' ,  getEnv ) ;  // get runtime environment 
2125    $app . get ( '/api/utils/backup' ,  gistBackup ) ;  // gist backup actions 
2226    $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 ) ; 
5328
5429    // Storage management 
5530    $app . route ( '/api/storage' ) 
@@ -95,9 +70,152 @@ export default function register($app) {
9570} 
9671
9772function  getEnv ( req ,  res )  { 
73+     if  ( req . query . share )  { 
74+         env . feature . share  =  true ; 
75+     } 
9876    success ( res ,  env ) ; 
9977} 
10078
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+ } 
101219async  function  refresh ( _ ,  res )  { 
102220    // 1. get GitHub avatar and artifact store 
103221    await  updateAvatar ( ) ; 
0 commit comments