@supabase/gotrue-js 2.71.0-rc.5 → 2.71.0-rc.6

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.
@@ -134,6 +134,16 @@ async function lockNoOp<R>(name: string, acquireTimeout: number, fn: () => Promi
134
134
  return await fn()
135
135
  }
136
136
 
137
+ /**
138
+ * Caches JWKS values for all clients created in the same environment. This is
139
+ * especially useful for shared-memory execution environments such as Vercel's
140
+ * Fluid Compute, AWS Lambda or Supabase's Edge Functions. Regardless of how
141
+ * many clients are created, if they share the same storage key they will use
142
+ * the same JWKS cache, significantly speeding up getClaims() with asymmetric
143
+ * JWTs.
144
+ */
145
+ const GLOBAL_JWKS: { [storageKey: string]: { cachedAt: number; jwks: { keys: JWK[] } } } = {}
146
+
137
147
  export default class GoTrueClient {
138
148
  private static nextInstanceID = 0
139
149
 
@@ -154,11 +164,26 @@ export default class GoTrueClient {
154
164
  protected storageKey: string
155
165
 
156
166
  protected flowType: AuthFlowType
167
+
157
168
  /**
158
169
  * The JWKS used for verifying asymmetric JWTs
159
170
  */
160
- protected jwks: { keys: JWK[] }
161
- protected jwks_cached_at: number
171
+ protected get jwks() {
172
+ return GLOBAL_JWKS[this.storageKey]?.jwks ?? { keys: [] }
173
+ }
174
+
175
+ protected set jwks(value: { keys: JWK[] }) {
176
+ GLOBAL_JWKS[this.storageKey] = { ...GLOBAL_JWKS[this.storageKey], jwks: value }
177
+ }
178
+
179
+ protected get jwks_cached_at() {
180
+ return GLOBAL_JWKS[this.storageKey]?.cachedAt ?? Number.MIN_SAFE_INTEGER
181
+ }
182
+
183
+ protected set jwks_cached_at(value: number) {
184
+ GLOBAL_JWKS[this.storageKey] = { ...GLOBAL_JWKS[this.storageKey], cachedAt: value }
185
+ }
186
+
162
187
  protected autoRefreshToken: boolean
163
188
  protected persistSession: boolean
164
189
  protected storage: SupportedStorage
@@ -242,8 +267,12 @@ export default class GoTrueClient {
242
267
  } else {
243
268
  this.lock = lockNoOp
244
269
  }
245
- this.jwks = { keys: [] }
246
- this.jwks_cached_at = Number.MIN_SAFE_INTEGER
270
+
271
+ if (!this.jwks) {
272
+ this.jwks = { keys: [] }
273
+ this.jwks_cached_at = Number.MIN_SAFE_INTEGER
274
+ }
275
+
247
276
  this.mfa = {
248
277
  verify: this._verify.bind(this),
249
278
  enroll: this._enroll.bind(this),
@@ -2946,11 +2975,13 @@ export default class GoTrueClient {
2946
2975
  return jwk
2947
2976
  }
2948
2977
 
2978
+ const now = Date.now()
2979
+
2949
2980
  // try fetching from cache
2950
2981
  jwk = this.jwks.keys.find((key) => key.kid === kid)
2951
2982
 
2952
2983
  // jwk exists and jwks isn't stale
2953
- if (jwk && this.jwks_cached_at + JWKS_TTL > Date.now()) {
2984
+ if (jwk && this.jwks_cached_at + JWKS_TTL > now) {
2954
2985
  return jwk
2955
2986
  }
2956
2987
  // jwk isn't cached in memory so we need to fetch it from the well-known endpoint
@@ -2963,8 +2994,10 @@ export default class GoTrueClient {
2963
2994
  if (!data.keys || data.keys.length === 0) {
2964
2995
  throw new AuthInvalidJwtError('JWKS is empty')
2965
2996
  }
2997
+
2966
2998
  this.jwks = data
2967
- this.jwks_cached_at = Date.now()
2999
+ this.jwks_cached_at = now
3000
+
2968
3001
  // Find the signing key
2969
3002
  jwk = data.keys.find((key: any) => key.kid === kid)
2970
3003
  if (!jwk) {
@@ -2974,12 +3007,35 @@ export default class GoTrueClient {
2974
3007
  }
2975
3008
 
2976
3009
  /**
2977
- * @experimental This method may change in future versions.
2978
- * @description Gets the claims from a JWT. If the JWT is symmetric JWTs, it will call getUser() to verify against the server. If the JWT is asymmetric, it will be verified against the JWKS using the WebCrypto API.
3010
+ * Extracts the JWT claims present in the access token by first verifying the
3011
+ * JWT against the server's JSON Web Key Set endpoint
3012
+ * `/.well-known/jwks.json` which is often cached, resulting in significantly
3013
+ * faster responses. Prefer this method over {@link #getUser} which always
3014
+ * sends a request to the Auth server for each JWT.
3015
+ *
3016
+ * If the project is not using an asymmetric JWT signing key (like ECC or
3017
+ * RSA) it always sends a request to the Auth server (similar to {@link
3018
+ * #getUser}) to verify the JWT.
3019
+ *
3020
+ * @param jwt An optional specific JWT you wish to verify, not the one you
3021
+ * can obtain from {@link #getSession}.
3022
+ * @param options Various additional options that allow you to customize the
3023
+ * behavior of this method.
2979
3024
  */
2980
3025
  async getClaims(
2981
3026
  jwt?: string,
2982
- jwks: { keys: JWK[] } = { keys: [] }
3027
+ options: {
3028
+ /**
3029
+ * @deprecated Please use options.jwks instead.
3030
+ */
3031
+ keys?: JWK[]
3032
+
3033
+ /** If set to `true` the `exp` claim will not be validated against the current time. */
3034
+ allowExpired?: boolean
3035
+
3036
+ /** If set, this JSON Web Key Set is going to have precedence over the cached value available on the server. */
3037
+ jwks?: { keys: JWK[] }
3038
+ } = {}
2983
3039
  ): Promise<
2984
3040
  | {
2985
3041
  data: { claims: JwtPayload; header: JwtHeader; signature: Uint8Array }
@@ -3005,8 +3061,10 @@ export default class GoTrueClient {
3005
3061
  raw: { header: rawHeader, payload: rawPayload },
3006
3062
  } = decodeJWT(token)
3007
3063
 
3008
- // Reject expired JWTs
3009
- validateExp(payload.exp)
3064
+ if (!options?.allowExpired) {
3065
+ // Reject expired JWTs should only happen if jwt argument was passed
3066
+ validateExp(payload.exp)
3067
+ }
3010
3068
 
3011
3069
  // If symmetric algorithm or WebCrypto API is unavailable, fallback to getUser()
3012
3070
  if (
@@ -3030,7 +3088,10 @@ export default class GoTrueClient {
3030
3088
  }
3031
3089
 
3032
3090
  const algorithm = getAlgorithm(header.alg)
3033
- const signingKey = await this.fetchJwk(header.kid, jwks)
3091
+ const signingKey = await this.fetchJwk(
3092
+ header.kid,
3093
+ options?.keys ? { keys: options.keys } : options?.jwks
3094
+ )
3034
3095
 
3035
3096
  // Convert JWK to CryptoKey
3036
3097
  const publicKey = await crypto.subtle.importKey('jwk', signingKey, algorithm, true, [
@@ -31,4 +31,4 @@ export const API_VERSIONS = {
31
31
 
32
32
  export const BASE64URL_REGEX = /^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$/i
33
33
 
34
- export const JWKS_TTL = 600000 // 10 minutes
34
+ export const JWKS_TTL = 10 * 60 * 1000 // 10 minutes
@@ -1 +1 @@
1
- export const version = '2.71.0-rc.5'
1
+ export const version = '2.71.0-rc.6'