@@ -6,147 +6,140 @@ import { ratelimitGenerator } from "./ratelimit.js";
66
77const { ADMIN_KEY } = process . env ;
88
9- const loginQuery = db . query ( `
9+ const loginQuery = db . prepare ( `
1010 INSERT INTO sessions (token, created, expires)
11- VALUES ($token, $created, $expires )
11+ VALUES (?, ?, ? )
1212` ) ;
13- const getValidTokenQuery = db . query ( `
14- SELECT * FROM sessions WHERE token = $token AND expires > $now LIMIT 1
13+ const getValidTokenQuery = db . prepare ( `
14+ SELECT * FROM sessions WHERE token = ? AND expires > ? LIMIT 1
1515` ) ;
1616
1717if ( ! ADMIN_KEY ) throw new Error ( "auth: Admin key missing. Please add one" ) ;
1818if ( ADMIN_KEY . length < 30 )
19- throw new Error (
20- "auth: Admin key too short. Please use one that's at least 30 characters" ,
21- ) ;
19+ throw new Error (
20+ "auth: Admin key too short. Please use one that's at least 30 characters"
21+ ) ;
2222
2323export const auth = new Elysia ( {
24- prefix : "/auth" ,
24+ prefix : "/auth" ,
2525} )
26- . use (
27- rateLimit ( {
28- duration : 30_000 ,
29- max : 20_000 ,
30- scoping : "scoped" ,
31- generator : ratelimitGenerator ,
32- } ) ,
33- )
34- . post ( "/login" , async ( { body, set, cookie } ) => {
35- const { admin_key } = body ;
36-
37- const a = Buffer . from ( admin_key , "utf8" ) ;
38- const b = Buffer . from ( ADMIN_KEY , "utf8" ) ;
39-
40- if ( ! a || ! b || a . length !== b . length ) {
41- set . status = 401 ;
42- return { success : false } ;
43- }
44-
45- if ( ! timingSafeEqual ( a , b ) ) {
46- set . status = 401 ;
47- return { success : false } ;
48- }
49-
50- if ( admin_key !== ADMIN_KEY ) {
51- // as a last check, in case an attacker somehow bypasses
52- // timingSafeEqual, we're checking AGAIN to see if the tokens
53- // are right.
54-
55- // yes, this is vulnerable to timing attacks, but those are
56- // hard to execute and literally just accepting an invalid token
57- // is worse.
58-
59- set . status = 401 ;
60- return { success : false } ;
61- }
62-
63- const session_token = randomBytes ( 30 ) . toString ( "hex" ) ;
64- const expires = Date . now ( ) + 30 * 24 * 60 * 60 * 1000 ; // 30 days
65- const created = Date . now ( ) ;
66-
67- const hashedToken = await Bun . password . hash ( session_token ) ;
68-
69- loginQuery . run ( {
70- $token : hashedToken ,
71- $created : created ,
72- $expires : expires ,
73- } ) ;
74-
75- cookie . cap_authed . set ( {
76- value : "yes" ,
77- expires : new Date ( expires ) ,
78- } ) ;
79-
80- return { success : true , session_token, hashed_token : hashedToken , expires } ;
81- } ) ;
26+ . use (
27+ rateLimit ( {
28+ duration : 30_000 ,
29+ max : 20_000 ,
30+ scoping : "scoped" ,
31+ generator : ratelimitGenerator ,
32+ } )
33+ )
34+ . post ( "/login" , async ( { body, set, cookie } ) => {
35+ const { admin_key } = body ;
36+
37+ const a = Buffer . from ( admin_key , "utf8" ) ;
38+ const b = Buffer . from ( ADMIN_KEY , "utf8" ) ;
39+
40+ if ( ! a || ! b || a . length !== b . length ) {
41+ set . status = 401 ;
42+ return { success : false } ;
43+ }
44+
45+ if ( ! timingSafeEqual ( a , b ) ) {
46+ set . status = 401 ;
47+ return { success : false } ;
48+ }
49+
50+ if ( admin_key !== ADMIN_KEY ) {
51+ // as a last check, in case an attacker somehow bypasses
52+ // timingSafeEqual, we're checking AGAIN to see if the tokens
53+ // are right.
54+
55+ // yes, this is vulnerable to timing attacks, but those are
56+ // hard to execute and literally just accepting an invalid token
57+ // is worse.
58+
59+ set . status = 401 ;
60+ return { success : false } ;
61+ }
62+
63+ const session_token = randomBytes ( 30 ) . toString ( "hex" ) ;
64+ const expires = Date . now ( ) + 30 * 24 * 60 * 60 * 1000 ; // 30 days
65+ const created = Date . now ( ) ;
66+
67+ const hashedToken = await Bun . password . hash ( session_token ) ;
68+
69+ loginQuery . run ( hashedToken , created , expires ) ;
70+
71+ cookie . cap_authed . set ( {
72+ value : "yes" ,
73+ expires : new Date ( expires ) ,
74+ } ) ;
75+
76+ return { success : true , session_token, hashed_token : hashedToken , expires } ;
77+ } ) ;
8278
8379export const authBeforeHandle = async ( { set, headers } ) => {
84- const { authorization } = headers ;
85-
86- set . headers [ "X-Content-Type-Options" ] = "nosniff" ;
87- set . headers [ "X-Frame-Options" ] = "DENY" ;
88- set . headers [ "X-XSS-Protection" ] = "1; mode=block" ;
89-
90- if ( authorization ?. startsWith ( "Bot " ) ) {
91- const botToken = authorization . replace ( "Bot " , "" ) . trim ( ) ;
92- const [ id , token ] = botToken . split ( "_" ) ;
93-
94- if ( ! id || ! token ) {
95- set . status = 401 ;
96- return { success : false , error : "Unauthorized. Invalid bot token." } ;
97- }
98-
99- const apiKey = db . query ( `SELECT * FROM api_keys WHERE id = $id` ) . get ( {
100- $id : id ,
101- } ) ;
102-
103- if ( ! apiKey ) {
104- set . status = 401 ;
105- return {
106- success : false ,
107- error : "Unauthorized. Deleted or non-existent bot token." ,
108- } ;
109- }
110-
111- if ( ! ( await Bun . password . verify ( token , apiKey . tokenHash ) ) ) {
112- set . status = 401 ;
113- return { success : false , error : "Unauthorized. Invalid bot token." } ;
114- }
115-
116- return ;
117- }
118-
119- if ( ! authorization || ! authorization . startsWith ( "Bearer " ) ) {
120- set . status = 401 ;
121- return {
122- success : false ,
123- error :
124- "Unauthorized. An API key or session token is required to use this endpoint." ,
125- } ;
126- }
127-
128- const { token, hash } = JSON . parse (
129- atob ( authorization . replace ( "Bearer " , "" ) . trim ( ) ) ,
130- ) ;
131-
132- const validToken = getValidTokenQuery . get ( {
133- $token : hash ,
134- $now : Date . now ( ) ,
135- } ) ;
136-
137- if ( ! validToken ) {
138- set . status = 401 ;
139- return {
140- success : false ,
141- error : "Unauthorized. An invalid session token was used." ,
142- } ;
143- }
144-
145- if ( ! ( await Bun . password . verify ( token , validToken . token ) ) ) {
146- set . status = 401 ;
147- return {
148- success : false ,
149- error : "Unauthorized. An invalid session token was used." ,
150- } ;
151- }
80+ const { authorization } = headers ;
81+
82+ set . headers [ "X-Content-Type-Options" ] = "nosniff" ;
83+ set . headers [ "X-Frame-Options" ] = "DENY" ;
84+ set . headers [ "X-XSS-Protection" ] = "1; mode=block" ;
85+
86+ if ( authorization ?. startsWith ( "Bot " ) ) {
87+ const botToken = authorization . replace ( "Bot " , "" ) . trim ( ) ;
88+ const [ id , token ] = botToken . split ( "_" ) ;
89+
90+ if ( ! id || ! token ) {
91+ set . status = 401 ;
92+ return { success : false , error : "Unauthorized. Invalid bot token." } ;
93+ }
94+
95+ const apiKey = await db
96+ . prepare ( `SELECT * FROM api_keys WHERE id = ?` )
97+ . get ( id ) ;
98+
99+ if ( ! apiKey || ! apiKey . tokenHash ) {
100+ set . status = 401 ;
101+ return {
102+ success : false ,
103+ error : "Unauthorized. Deleted or non-existent bot token." ,
104+ } ;
105+ }
106+
107+ if ( ! ( await Bun . password . verify ( token , apiKey . tokenHash ) ) ) {
108+ set . status = 401 ;
109+ return { success : false , error : "Unauthorized. Invalid bot token." } ;
110+ }
111+
112+ return ;
113+ }
114+
115+ if ( ! authorization || ! authorization . startsWith ( "Bearer " ) ) {
116+ set . status = 401 ;
117+ return {
118+ success : false ,
119+ error :
120+ "Unauthorized. An API key or session token is required to use this endpoint." ,
121+ } ;
122+ }
123+
124+ const { token, hash } = JSON . parse (
125+ atob ( authorization . replace ( "Bearer " , "" ) . trim ( ) )
126+ ) ;
127+
128+ const validToken = await getValidTokenQuery . get ( hash , Date . now ( ) ) ;
129+
130+ if ( ! validToken ) {
131+ set . status = 401 ;
132+ return {
133+ success : false ,
134+ error : "Unauthorized. An invalid session token was used." ,
135+ } ;
136+ }
137+
138+ if ( ! ( await Bun . password . verify ( token , validToken . token ) ) ) {
139+ set . status = 401 ;
140+ return {
141+ success : false ,
142+ error : "Unauthorized. An invalid session token was used." ,
143+ } ;
144+ }
152145} ;
0 commit comments