1
1
import type { TeamModel } from '../../../orm/src/models/Team'
2
2
import type { UserModel } from '../../../orm/src/models/User'
3
+ import { randomBytes } from 'node:crypto'
3
4
import { HttpError } from '@stacksjs/error-handling'
4
5
import { request } from '@stacksjs/router'
5
6
import { verifyHash } from '@stacksjs/security'
6
- import { sign , verify } from '@stacksjs/security' // Assuming this exists, if not we can use 'jsonwebtoken'
7
7
import AccessToken from '../../../orm/src/models/AccessToken'
8
8
import Team from '../../../orm/src/models/Team'
9
9
import User from '../../../orm/src/models/User'
@@ -14,16 +14,8 @@ interface Credentials {
14
14
[ key : string ] : string | undefined
15
15
}
16
16
17
- interface TokenPayload {
18
- userId : number
19
- teamId ?: number
20
- email : string
21
- exp ?: number
22
- }
23
-
24
17
type AuthToken = `${number } :${number } :${string } `
25
18
26
- const TOKEN_EXPIRY = 60 * 60 * 24 // 24 hours
27
19
const authConfig = { username : 'email' , password : 'password' }
28
20
29
21
let authUser : UserModel | null = null
@@ -45,43 +37,78 @@ export async function attempt(credentials: Credentials): Promise<boolean> {
45
37
return false
46
38
}
47
39
48
- export async function generateAccessToken ( user : UserModel ) : Promise < string > {
49
- const payload : TokenPayload = {
50
- userId : user . id as number ,
51
- email : user . email as string ,
52
- exp : Math . floor ( Date . now ( ) / 1000 ) + TOKEN_EXPIRY ,
53
- }
40
+ export async function createAccessToken ( user : UserModel , teamId ?: number ) : Promise < AuthToken > {
41
+ const token = randomBytes ( 40 ) . toString ( 'hex' )
54
42
55
- return sign ( payload , process . env . JWT_SECRET || 'your-secret-key' )
56
- }
43
+ const accessToken = await AccessToken . create ( {
44
+ team_id : teamId ,
45
+ token,
46
+ name : 'auth-token' ,
47
+ expires_at : new Date ( Date . now ( ) + 1000 * 60 * 60 * 24 * 30 ) , // 30 days
48
+ } )
57
49
58
- export async function verifyAccessToken ( token : string ) : Promise < TokenPayload | null > {
59
- try {
60
- const payload = await verify ( token , process . env . JWT_SECRET || 'your-secret-key' ) as TokenPayload
61
- return payload
62
- }
63
- catch ( error ) {
64
- return null
65
- }
50
+ if ( ! accessToken ?. id )
51
+ throw new HttpError ( 500 , 'Failed to create access token' )
52
+
53
+ return `${ accessToken . id } :${ teamId || 0 } :${ token } `
66
54
}
67
55
68
- export async function login ( credentials : Credentials ) : Promise < { token : string } | null > {
56
+ export async function login ( credentials : Credentials ) : Promise < { token : AuthToken } | null > {
69
57
const isValid = await attempt ( credentials )
70
58
71
59
if ( ! isValid || ! authUser )
72
60
return null
73
61
74
- const token = await generateAccessToken ( authUser )
62
+ // Get user's primary team
63
+ const teams = await authUser . userTeams ( )
64
+ const primaryTeam = teams [ 0 ]
65
+
66
+ const token = await createAccessToken ( authUser , primaryTeam ?. id )
75
67
return { token }
76
68
}
77
69
70
+ export async function validateToken ( token : string ) : Promise < boolean > {
71
+ const parts = token . split ( ':' )
72
+
73
+ if ( parts . length !== 3 )
74
+ return false
75
+
76
+ const [ tokenId , teamId , plainToken ] = parts
77
+
78
+ const accessToken = await AccessToken . where ( 'id' , Number ( tokenId ) )
79
+ . where ( 'token' , plainToken )
80
+ . where ( 'team_id' , Number ( teamId ) )
81
+ . first ( )
82
+
83
+ if ( ! accessToken )
84
+ return false
85
+
86
+ // Check if token is expired
87
+ if ( accessToken . expires_at && new Date ( accessToken . expires_at ) < new Date ( ) )
88
+ return false
89
+
90
+ // Update last used timestamp
91
+ await AccessToken . where ( 'id' , accessToken . id ) . update ( {
92
+ last_used_at : new Date ( ) ,
93
+ } )
94
+
95
+ return true
96
+ }
97
+
78
98
export async function getUserFromToken ( token : string ) : Promise < UserModel | null > {
79
- const payload = await verifyAccessToken ( token )
99
+ const parts = token . split ( ':' )
100
+
101
+ if ( parts . length !== 3 )
102
+ return null
103
+
104
+ const [ tokenId ] = parts
105
+
106
+ const accessToken = await AccessToken . where ( 'id' , Number ( tokenId ) ) . first ( )
80
107
81
- if ( ! payload )
108
+ if ( ! accessToken ?. user_id )
82
109
return null
83
110
84
- return await User . find ( payload . userId )
111
+ return await User . find ( accessToken . user_id )
85
112
}
86
113
87
114
export async function team ( ) : Promise < TeamModel | undefined > {
@@ -114,21 +141,22 @@ export async function team(): Promise<TeamModel | undefined> {
114
141
return await Team . find ( Number ( accessToken ?. team_id ) )
115
142
}
116
143
117
- export async function authToken ( ) : Promise < AuthToken | undefined > {
118
- if ( authUser ) {
119
- const teams = await authUser . userTeams ( )
120
- const team = teams [ 0 ]
121
- const accessTokens = await team . teamAccessTokens ( )
122
- const accessToken = accessTokens [ 0 ]
123
- const tokenId = accessToken ?. id
144
+ export async function revokeToken ( token : string ) : Promise < void > {
145
+ const parts = token . split ( ':' )
124
146
125
- if ( ! accessToken )
126
- throw new HttpError ( 500 , 'Error generating token!' )
147
+ if ( parts . length !== 3 )
148
+ return
127
149
128
- return `${ tokenId } :${ team . id } :${ accessToken . token } `
129
- }
150
+ const [ tokenId ] = parts
151
+
152
+ await AccessToken . where ( 'id' , Number ( tokenId ) ) . delete ( )
130
153
}
131
154
132
155
export async function logout ( ) : Promise < void > {
156
+ const bearerToken = request . bearerToken ( )
157
+
158
+ if ( bearerToken )
159
+ await revokeToken ( bearerToken )
160
+
133
161
authUser = null
134
162
}
0 commit comments