@stacksjs/auth 0.69.2 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,45 +1,114 @@
1
+ import type { UserModel, UsersTable } from '../../../orm/src/models/User';
2
+
1
3
  declare interface Credentials {
2
4
  password: string | undefined
5
+ email: string | undefined
3
6
  [key: string]: string | undefined
4
7
  }
5
8
  declare type AuthToken = `${number}:${number}:${string}`
6
9
 
7
10
  const authConfig = { username: 'email', password: 'password' }
8
11
 
9
- let authUser: any
12
+ let authUser: UserModel | null = null
10
13
 
11
14
  export async function attempt(credentials: Credentials): Promise<boolean> {
12
15
  let hashCheck = false
13
16
 
14
- const user = await User.where(authConfig.username, credentials[authConfig.username]).first()
17
+ const user = await User.where(authConfig.username as keyof UsersTable, credentials[authConfig.username]).first()
15
18
  const authPass = credentials[authConfig.password]
16
19
 
17
20
  if (typeof authPass === 'string' && user?.password)
18
- hashCheck = await verifyHash(authPass, user?.password, 'bcrypt')
19
-
20
- if (hashCheck) {
21
- if (user) {
22
- authUser = user
21
+ hashCheck = await verifyHash(authPass, user.password, 'bcrypt')
23
22
 
24
- return true
25
- }
23
+ if (hashCheck && user) {
24
+ authUser = user
25
+ return true
26
26
  }
27
27
 
28
28
  return false
29
29
  }
30
30
 
31
+ export async function createAccessToken(user: UserModel, teamId?: number): Promise<AuthToken> {
32
+ const token = randomBytes(40).toString('hex')
33
+
34
+ const accessToken = await AccessToken.create({
35
+ team_id: teamId,
36
+ token,
37
+ name: 'auth-token',
38
+ expires_at: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
39
+ })
40
+
41
+ if (!accessToken?.id)
42
+ throw new HttpError(500, 'Failed to create access token')
43
+
44
+ return `${accessToken.id}:${teamId || 0}:${token}`
45
+ }
46
+
47
+ export async function login(credentials: Credentials): Promise<{ token: AuthToken } | null> {
48
+ const isValid = await attempt(credentials)
49
+
50
+ if (!isValid || !authUser)
51
+ return null
52
+
53
+ const teams = await authUser.userTeams()
54
+ const primaryTeam = teams[0]
55
+
56
+ const token = await createAccessToken(authUser, primaryTeam?.id)
57
+ return { token }
58
+ }
59
+
60
+ export async function validateToken(token: string): Promise<boolean> {
61
+ const parts = token.split(':')
62
+
63
+ if (parts.length !== 3)
64
+ return false
65
+
66
+ const [tokenId, teamId, plainToken] = parts
67
+
68
+ const accessToken = await AccessToken.where('id', Number(tokenId))
69
+ .where('token', plainToken)
70
+ .where('team_id', Number(teamId))
71
+ .first()
72
+
73
+ if (!accessToken)
74
+ return false
75
+
76
+ if (accessToken.expires_at && new Date(accessToken.expires_at) < new Date())
77
+ return false
78
+
79
+ await AccessToken.where('id', accessToken.id).update({
80
+ last_used_at: new Date(),
81
+ })
82
+
83
+ return true
84
+ }
85
+
86
+ export async function getUserFromToken(token: string): Promise<UserModel | undefined> {
87
+ const parts = token.split(':')
88
+
89
+ if (parts.length !== 3)
90
+ return undefined
91
+
92
+ const [tokenId] = parts
93
+
94
+ const accessToken = await AccessToken.where('id', Number(tokenId)).first()
95
+
96
+ if (!accessToken?.user_id)
97
+ return undefined
98
+
99
+ return await User.find(accessToken.user_id)
100
+ }
101
+
31
102
  export async function team(): Promise<TeamModel | undefined> {
32
103
  if (authUser) {
33
104
  const teams = await authUser.userTeams()
34
-
35
- return teams[0].team
105
+ return teams[0]
36
106
  }
37
107
 
38
108
  const bearerToken = request.bearerToken()
39
109
 
40
- if (!bearerToken) {
110
+ if (!bearerToken)
41
111
  return undefined
42
- }
43
112
 
44
113
  const parts = bearerToken.split(':')
45
114
 
@@ -50,31 +119,32 @@ export async function team(): Promise<TeamModel | undefined> {
50
119
  const teamId = parts[1]
51
120
  const plainString = parts[2]
52
121
 
53
- const accessToken = await AccessToken.where('id', Number(tokenId)).where('token', plainString).first()
122
+ const accessToken = await AccessToken.where('id', Number(tokenId))
123
+ .where('token', plainString)
124
+ .first()
54
125
 
55
- if (Number(teamId) !== Number(accessToken?.team_id)) {
126
+ if (Number(teamId) !== Number(accessToken?.team_id))
56
127
  return undefined
57
- }
58
-
59
- const team = await Team.find(Number(accessToken?.team_id))
60
128
 
61
- return team
129
+ return await Team.find(Number(accessToken?.team_id))
62
130
  }
63
131
 
64
- export async function authToken(): Promise<AuthToken | undefined> {
65
- if (authUser) {
66
- const teams = await authUser.userTeams()
132
+ export async function revokeToken(token: string): Promise<void> {
133
+ const parts = token.split(':')
67
134
 
68
- const team = teams[0]
135
+ if (parts.length !== 3)
136
+ return
69
137
 
70
- const accessTokens = await team.teamAccessTokens()
71
- const accessToken = accessTokens[0]
138
+ const [tokenId] = parts
72
139
 
73
- const tokenId = accessToken?.id
140
+ await AccessToken.where('id', Number(tokenId)).delete()
141
+ }
74
142
 
75
- if (!accessToken)
76
- throw new HttpError(500, 'Error generating token!')
143
+ export async function logout(): Promise<void> {
144
+ const bearerToken = request.bearerToken()
77
145
 
78
- return `${tokenId}:${team.id}:${accessToken.token}`
79
- }
146
+ if (bearerToken)
147
+ await revokeToken(bearerToken)
148
+
149
+ authUser = null
80
150
  }