@@ -13,144 +13,146 @@ const TokenInfo = z.strictObject({
1313 expiresAt : z . date ( ) ,
1414} ) ;
1515
16- export async function generateToken (
17- db : Pool ,
18- userID : string ,
19- ) : Promise < [ token : string , expiry : Date ] > {
20- const secret = crypto . randomBytes ( 16 ) ;
21- const expiresAt = new Date ( Date . now ( ) + TOKEN_LIFETIME ) ;
22-
23- const hash = Buffer . from ( await crypto . subtle . digest ( ALGORITHM , secret ) ) ;
24-
25- await db . query (
26- `
27- INSERT INTO "api_tokens" (
28- "userID",
29- "hash",
30- "expiresAt"
31- )
32- VALUES ($1, $2, $3)
33- ` ,
34- [ userID , hash , expiresAt ] ,
35- ) ;
36-
37- return [
38- BigInt ( userID ) . toString ( 16 ) + "." + secret . toString ( "hex" ) ,
39- expiresAt ,
40- ] ;
41- }
16+ export namespace tokensTable {
17+ export async function generateAndInsert (
18+ db : Pool ,
19+ userID : string ,
20+ ) : Promise < [ token : string , expiry : Date ] > {
21+ const secret = crypto . randomBytes ( 16 ) ;
22+ const expiresAt = new Date ( Date . now ( ) + TOKEN_LIFETIME ) ;
23+
24+ const hash = Buffer . from ( await crypto . subtle . digest ( ALGORITHM , secret ) ) ;
4225
43- async function tokenKey ( token : string ) : Promise < [ bigint , Buffer ] | null > {
44- const splitIndex = token . indexOf ( "." ) ;
26+ await db . query (
27+ `
28+ INSERT INTO "api_tokens" (
29+ "userID",
30+ "hash",
31+ "expiresAt"
32+ )
33+ VALUES ($1, $2, $3)
34+ ` ,
35+ [ userID , hash , expiresAt ] ,
36+ ) ;
4537
46- if ( splitIndex === - 1 ) {
47- return null ;
38+ return [
39+ BigInt ( userID ) . toString ( 16 ) + "." + secret . toString ( "hex" ) ,
40+ expiresAt ,
41+ ] ;
4842 }
4943
50- const userIDPart = token . slice ( 0 , splitIndex ) ;
51- const secretPart = token . slice ( splitIndex + 1 ) ;
44+ async function tokenKey ( token : string ) : Promise < [ bigint , Buffer ] | null > {
45+ const splitIndex = token . indexOf ( "." ) ;
5246
53- if ( userIDPart . length === 0 || secretPart . length === 0 ) {
54- return null ;
55- }
47+ if ( splitIndex === - 1 ) {
48+ return null ;
49+ }
50+
51+ const userIDPart = token . slice ( 0 , splitIndex ) ;
52+ const secretPart = token . slice ( splitIndex + 1 ) ;
5653
57- try {
58- var userID = BigInt ( "0x" + userIDPart ) ;
59- } catch ( error ) {
60- if ( ! ( error instanceof SyntaxError ) ) {
61- throw error ;
54+ if ( userIDPart . length === 0 || secretPart . length === 0 ) {
55+ return null ;
6256 }
6357
64- return null ;
65- }
58+ try {
59+ var userID = BigInt ( "0x" + userIDPart ) ;
60+ } catch ( error ) {
61+ if ( ! ( error instanceof SyntaxError ) ) {
62+ throw error ;
63+ }
6664
67- const secretBuffer = Buffer . from ( secretPart , "hex" ) ;
68- const hash = Buffer . from (
69- await crypto . subtle . digest ( ALGORITHM , secretBuffer ) ,
70- ) ;
65+ return null ;
66+ }
7167
72- return [ userID , hash ] ;
73- }
68+ const secretBuffer = Buffer . from ( secretPart , "hex" ) ;
69+ const hash = Buffer . from (
70+ await crypto . subtle . digest ( ALGORITHM , secretBuffer ) ,
71+ ) ;
7472
75- /**
76- * @returns user ID if valid
77- */
78- export async function validateToken (
79- db : Pool ,
80- token : string ,
81- ) : Promise < string | null > {
82- const key : [ bigint , Buffer ] | null = await tokenKey ( token ) ;
83-
84- if ( key === null ) {
85- return null ;
73+ return [ userID , hash ] ;
8674 }
8775
88- const result = await db . query (
89- `
90- SELECT "userID", "expiresAt"
91- FROM "api_tokens"
92- WHERE "userID" = $1 AND "hash" = $2
93- ` ,
94- key ,
95- ) ;
96-
97- if ( result . rowCount !== 1 ) {
98- return null ;
76+ /**
77+ * @returns user ID if valid
78+ */
79+ export async function validate (
80+ db : Pool ,
81+ token : string ,
82+ ) : Promise < string | null > {
83+ const key : [ bigint , Buffer ] | null = await tokenKey ( token ) ;
84+
85+ if ( key === null ) {
86+ return null ;
87+ }
88+
89+ const result = await db . query (
90+ `
91+ SELECT "userID", "expiresAt"
92+ FROM "api_tokens"
93+ WHERE "userID" = $1 AND "hash" = $2
94+ ` ,
95+ key ,
96+ ) ;
97+
98+ if ( result . rowCount !== 1 ) {
99+ return null ;
100+ }
101+
102+ const { expiresAt, userID } = dbParse ( TokenInfo , result . rows [ 0 ] ) ;
103+
104+ if ( Date . now ( ) >= expiresAt . getTime ( ) ) {
105+ await db . query (
106+ `
107+ DELETE FROM "api_tokens"
108+ WHERE "userID" = $1 AND "hash" = $2
109+ ` ,
110+ key ,
111+ ) ;
112+ return null ;
113+ }
114+
115+ if ( Date . now ( ) - expiresAt . getTime ( ) >= TOKEN_REFRESH_THRESHOLD ) {
116+ await db . query (
117+ `
118+ UPDATE "api_tokens"
119+ SET "expiresAt" = $1
120+ WHERE "userID" = $2 AND "hash" = $3
121+ ` ,
122+ [ new Date ( Date . now ( ) + TOKEN_LIFETIME ) , userID , key [ 1 ] ] ,
123+ ) ;
124+ }
125+
126+ return userID ;
99127 }
100128
101- const { expiresAt, userID } = dbParse ( TokenInfo , result . rows [ 0 ] ) ;
129+ export async function remove ( db : Pool , token : string ) : Promise < boolean > {
130+ const key = await tokenKey ( token ) ;
102131
103- if ( Date . now ( ) >= expiresAt . getTime ( ) ) {
104- await db . query (
132+ if ( key === null ) {
133+ return false ;
134+ }
135+
136+ const result = await db . query (
105137 `
106138 DELETE FROM "api_tokens"
107139 WHERE "userID" = $1 AND "hash" = $2
108140 ` ,
109141 key ,
110142 ) ;
111- return null ;
143+
144+ return result . rowCount === 1 ;
112145 }
113146
114- if ( Date . now ( ) - expiresAt . getTime ( ) >= TOKEN_REFRESH_THRESHOLD ) {
115- await db . query (
147+ export async function removeExpiredTokens ( db : Pool ) : Promise < number > {
148+ const result = await db . query (
116149 `
117- UPDATE "api_tokens"
118- SET "expiresAt" = $1
119- WHERE "userID" = $2 AND "hash" = $3
150+ DELETE FROM "api_tokens"
151+ WHERE "expiresAt" <= $1
120152 ` ,
121- [ new Date ( Date . now ( ) + TOKEN_LIFETIME ) , userID , key [ 1 ] ] ,
153+ [ new Date ( ) ] ,
122154 ) ;
123- }
124-
125- return userID ;
126- }
127-
128- export async function deleteToken ( db : Pool , token : string ) : Promise < boolean > {
129- const key = await tokenKey ( token ) ;
130155
131- if ( key === null ) {
132- return false ;
156+ return result . rowCount ?? 0 ;
133157 }
134-
135- const result = await db . query (
136- `
137- DELETE FROM "api_tokens"
138- WHERE "userID" = $1 AND "hash" = $2
139- ` ,
140- key ,
141- ) ;
142-
143- return result . rowCount === 1 ;
144- }
145-
146- export async function deleteExpiredTokens ( db : Pool ) : Promise < number > {
147- const result = await db . query (
148- `
149- DELETE FROM "api_tokens"
150- WHERE "expiresAt" <= $1
151- ` ,
152- [ new Date ( ) ] ,
153- ) ;
154-
155- return result . rowCount ?? 0 ;
156158}
0 commit comments