@growth-labs/auth 0.11.0 → 0.12.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.
- package/dist/utils/client-secret-header.d.ts +2 -0
- package/dist/utils/client-secret-header.d.ts.map +1 -0
- package/dist/utils/client-secret-header.js +8 -0
- package/dist/utils/client-secret-header.js.map +1 -0
- package/dist/utils/client-secret.d.ts +1 -1
- package/dist/utils/client-secret.d.ts.map +1 -1
- package/dist/utils/client-secret.js +1 -7
- package/dist/utils/client-secret.js.map +1 -1
- package/dist/utils/cookies.d.ts.map +1 -1
- package/dist/utils/cookies.js +8 -1
- package/dist/utils/cookies.js.map +1 -1
- package/dist/utils/tokens.d.ts +9 -0
- package/dist/utils/tokens.d.ts.map +1 -1
- package/dist/utils/tokens.js +33 -2
- package/dist/utils/tokens.js.map +1 -1
- package/dist/workers.d.ts +89 -0
- package/dist/workers.d.ts.map +1 -0
- package/dist/workers.js +516 -0
- package/dist/workers.js.map +1 -0
- package/package.json +21 -2
- package/src/utils/client-secret-header.ts +8 -0
- package/src/utils/client-secret.ts +1 -9
- package/src/utils/cookies.ts +6 -1
- package/src/utils/tokens.ts +46 -2
- package/src/workers.ts +758 -0
package/src/utils/tokens.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createLocalJWKSet, jwtVerify } from 'jose'
|
|
2
2
|
import { JWTExpired } from 'jose/errors'
|
|
3
3
|
import type { TokenRefreshResult } from '../types.js'
|
|
4
|
-
import { createClientSecretBasicAuthHeader } from './client-secret.js'
|
|
4
|
+
import { createClientSecretBasicAuthHeader } from './client-secret-header.js'
|
|
5
|
+
import { extractIdentityId } from './identity.js'
|
|
5
6
|
|
|
6
7
|
type JWKSFunction = ReturnType<typeof createLocalJWKSet>
|
|
7
8
|
|
|
@@ -69,6 +70,9 @@ export interface VerifiedClaims {
|
|
|
69
70
|
image?: string
|
|
70
71
|
synthetic?: boolean
|
|
71
72
|
purpose?: string
|
|
73
|
+
userId?: string
|
|
74
|
+
roles?: string[]
|
|
75
|
+
audiences?: string[]
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
export interface TokenPayloadClaims extends VerifiedClaims {
|
|
@@ -103,6 +107,12 @@ export interface VerifyAccessTokenOptions {
|
|
|
103
107
|
audience?: string | string[]
|
|
104
108
|
/** Signature algorithms to accept. Defaults to `['ES256']` (identity-platform's signing alg). */
|
|
105
109
|
algorithms?: string[]
|
|
110
|
+
/** Fetch signing keys from this backchannel base while still enforcing `issuer`. */
|
|
111
|
+
jwksIssuer?: string
|
|
112
|
+
/** Enforce the identity-platform user access-token claim contract. */
|
|
113
|
+
requireUserToken?: boolean
|
|
114
|
+
/** Permitted JWT clock skew in seconds for strict user-token validation. */
|
|
115
|
+
clockSkew?: number
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
async function verifyAccessTokenPayload(
|
|
@@ -113,15 +123,38 @@ async function verifyAccessTokenPayload(
|
|
|
113
123
|
try {
|
|
114
124
|
const header = decodeJwtHeader(token)
|
|
115
125
|
const kid = typeof header.kid === 'string' ? header.kid : undefined
|
|
116
|
-
const jwks = await getJWKS(issuer, kid)
|
|
126
|
+
const jwks = await getJWKS(options.jwksIssuer ?? issuer, kid)
|
|
117
127
|
const { payload } = await jwtVerify(token, jwks, {
|
|
118
128
|
issuer,
|
|
119
129
|
audience: options.audience,
|
|
120
130
|
algorithms: options.algorithms ?? ['ES256'],
|
|
131
|
+
clockTolerance: options.clockSkew,
|
|
121
132
|
})
|
|
122
133
|
if (payload.mode !== 'access') {
|
|
123
134
|
throw new Error(`unexpected token mode: ${String(payload.mode)}`)
|
|
124
135
|
}
|
|
136
|
+
if (options.requireUserToken) {
|
|
137
|
+
const properties = payload.properties
|
|
138
|
+
if (
|
|
139
|
+
payload.type !== 'user' ||
|
|
140
|
+
typeof payload.iat !== 'number' ||
|
|
141
|
+
typeof payload.exp !== 'number' ||
|
|
142
|
+
typeof payload.sub !== 'string' ||
|
|
143
|
+
typeof properties !== 'object' ||
|
|
144
|
+
properties === null
|
|
145
|
+
) {
|
|
146
|
+
throw new Error('required user access-token claims are missing')
|
|
147
|
+
}
|
|
148
|
+
const userId = extractIdentityId(payload.sub)
|
|
149
|
+
if (
|
|
150
|
+
payload.iat > Math.floor(Date.now() / 1000) + (options.clockSkew ?? 0) ||
|
|
151
|
+
(properties as Record<string, unknown>).userId !== userId ||
|
|
152
|
+
typeof (properties as Record<string, unknown>).email !== 'string' ||
|
|
153
|
+
(properties as Record<string, unknown>).email === ''
|
|
154
|
+
) {
|
|
155
|
+
throw new Error('access-token identity claims are inconsistent')
|
|
156
|
+
}
|
|
157
|
+
}
|
|
125
158
|
return payload as Record<string, unknown>
|
|
126
159
|
} catch (err) {
|
|
127
160
|
if (!isExpiredJwtError(err)) {
|
|
@@ -139,6 +172,12 @@ export async function verifyAccessToken(
|
|
|
139
172
|
const payload = await verifyAccessTokenPayload(issuer, token, options)
|
|
140
173
|
if (!payload) return null
|
|
141
174
|
const claims = extractTokenClaims(payload)
|
|
175
|
+
const properties = (payload.properties ?? {}) as Record<string, unknown>
|
|
176
|
+
const audience = Array.isArray(payload.aud)
|
|
177
|
+
? payload.aud.filter((value): value is string => typeof value === 'string')
|
|
178
|
+
: typeof payload.aud === 'string'
|
|
179
|
+
? [payload.aud]
|
|
180
|
+
: []
|
|
142
181
|
return {
|
|
143
182
|
sub: claims.sub,
|
|
144
183
|
email: claims.email,
|
|
@@ -146,6 +185,11 @@ export async function verifyAccessToken(
|
|
|
146
185
|
image: claims.image,
|
|
147
186
|
synthetic: payload.synthetic === true ? true : undefined,
|
|
148
187
|
purpose: typeof payload.purpose === 'string' ? payload.purpose : undefined,
|
|
188
|
+
userId: typeof properties.userId === 'string' ? properties.userId : undefined,
|
|
189
|
+
roles: Array.isArray(payload.roles)
|
|
190
|
+
? payload.roles.filter((value): value is string => typeof value === 'string')
|
|
191
|
+
: undefined,
|
|
192
|
+
audiences: audience,
|
|
149
193
|
}
|
|
150
194
|
}
|
|
151
195
|
|