@tern-secure/backend 1.2.0-canary.v20251030165007 → 1.2.0-canary.v20251108045933

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.
Files changed (42) hide show
  1. package/dist/__tests__/request.test.d.ts +2 -0
  2. package/dist/__tests__/request.test.d.ts.map +1 -0
  3. package/dist/admin/index.js +31 -8
  4. package/dist/admin/index.js.map +1 -1
  5. package/dist/admin/index.mjs +17 -8
  6. package/dist/admin/index.mjs.map +1 -1
  7. package/dist/admin/nextSessionTernSecure.d.ts.map +1 -1
  8. package/dist/admin/sessionTernSecure.d.ts.map +1 -1
  9. package/dist/auth/getauth.d.ts +1 -0
  10. package/dist/auth/getauth.d.ts.map +1 -1
  11. package/dist/auth/index.js +49 -31
  12. package/dist/auth/index.js.map +1 -1
  13. package/dist/auth/index.mjs +3 -3
  14. package/dist/{chunk-IBABNFOK.mjs → chunk-ASGV4MFO.mjs} +2 -2
  15. package/dist/{chunk-5AP2WM3W.mjs → chunk-DDUNOEIM.mjs} +20 -31
  16. package/dist/chunk-DDUNOEIM.mjs.map +1 -0
  17. package/dist/{chunk-VY5FVZL2.mjs → chunk-DFAJCSBJ.mjs} +17 -3
  18. package/dist/chunk-DFAJCSBJ.mjs.map +1 -0
  19. package/dist/{chunk-A5G3CWO5.mjs → chunk-MS6L7M3C.mjs} +9 -4
  20. package/dist/chunk-MS6L7M3C.mjs.map +1 -0
  21. package/dist/constants.d.ts +13 -1
  22. package/dist/constants.d.ts.map +1 -1
  23. package/dist/index.js +156 -39
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +121 -11
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/jwt/index.js +19 -30
  28. package/dist/jwt/index.js.map +1 -1
  29. package/dist/jwt/index.mjs +1 -1
  30. package/dist/jwt/verifyJwt.d.ts.map +1 -1
  31. package/dist/tokens/authstate.d.ts +16 -4
  32. package/dist/tokens/authstate.d.ts.map +1 -1
  33. package/dist/tokens/c-authenticateRequestProcessor.d.ts +5 -0
  34. package/dist/tokens/c-authenticateRequestProcessor.d.ts.map +1 -1
  35. package/dist/tokens/request.d.ts.map +1 -1
  36. package/dist/tokens/types.d.ts +4 -0
  37. package/dist/tokens/types.d.ts.map +1 -1
  38. package/package.json +9 -7
  39. package/dist/chunk-5AP2WM3W.mjs.map +0 -1
  40. package/dist/chunk-A5G3CWO5.mjs.map +0 -1
  41. package/dist/chunk-VY5FVZL2.mjs.map +0 -1
  42. /package/dist/{chunk-IBABNFOK.mjs.map → chunk-ASGV4MFO.mjs.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokens/keys.ts","../src/tokens/verify.ts","../src/auth/getauth.ts"],"sourcesContent":["import { type RemoteJWKSetOptions } from 'jose';\n\nimport {\n CACHE_CONTROL_REGEX,\n DEFAULT_CACHE_DURATION,\n GOOGLE_PUBLIC_KEYS_URL,\n MAX_CACHE_LAST_UPDATED_AT_SECONDS\n} from '../constants';\nimport { TokenVerificationError, TokenVerificationErrorReason } from '../utils/errors';\n\nexport type PublicKeys = { [key: string]: string };\n\ninterface PublicKeysResponse {\n keys: PublicKeys;\n expiresAt: number;\n}\n\nexport type LoadJWKFromRemoteOptions = RemoteJWKSetOptions & {\n kid: string;\n keyURL?: string;\n skipJwksCache?: boolean;\n};\n\ntype CertificateCache = Record<string, string>;\n\nlet cache: CertificateCache = {};\nlet lastUpdatedAt = 0;\nlet googleExpiresAt = 0;\n\nfunction getFromCache(kid: string) {\n return cache[kid];\n}\n\nfunction getCacheValues() {\n return Object.values(cache);\n}\n\nfunction setInCache(kid: string, certificate: string, shouldExpire = true) {\n cache[kid] = certificate;\n lastUpdatedAt = shouldExpire ? Date.now() : -1;\n}\n\nasync function fetchPublicKeys(keyUrl: string): Promise<PublicKeysResponse> {\n const url = new URL(keyUrl);\n const response = await fetch(url);\n if (!response.ok) {\n throw new TokenVerificationError({\n message: `Error loading public keys from ${url.href} with code=${response.status} `,\n reason: TokenVerificationErrorReason.TokenInvalid,\n });\n }\n\n const data = await response.json();\n const expiresAt = getExpiresAt(response);\n\n return {\n keys: data,\n expiresAt,\n };\n}\n\nexport async function loadJWKFromRemote({\n keyURL = GOOGLE_PUBLIC_KEYS_URL,\n skipJwksCache,\n kid,\n}: LoadJWKFromRemoteOptions): Promise<string> {\n if (skipJwksCache || isCacheExpired() || !getFromCache(kid)) {\n const { keys, expiresAt } = await fetchPublicKeys(keyURL);\n\n if (!keys || Object.keys(keys).length === 0) {\n throw new TokenVerificationError({\n message: `The JWKS endpoint ${keyURL} returned no keys`,\n reason: TokenVerificationErrorReason.RemoteJWKFailedToLoad,\n });\n }\n googleExpiresAt = expiresAt;\n\n Object.entries(keys).forEach(([keyId, cert]) => {\n setInCache(keyId, cert);\n });\n }\n const cert = getFromCache(kid);\n if (!cert) {\n getCacheValues();\n const availableKids = Object.keys(cache).sort().join(', ');\n\n throw new TokenVerificationError({\n message: `No public key found for kid \"${kid}\". Available kids: [${availableKids}]`,\n reason: TokenVerificationErrorReason.TokenInvalid,\n });\n }\n return cert;\n}\n\nfunction isCacheExpired() {\n const now = Date.now();\n if (lastUpdatedAt === -1) {\n return false;\n }\n\n const cacheAge = now - lastUpdatedAt;\n const maxCacheAge = MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1000;\n const localCacheExpired = cacheAge >= maxCacheAge;\n const googleCacheExpired = now >= googleExpiresAt;\n\n const isExpired = localCacheExpired || googleCacheExpired;\n\n if (isExpired) {\n cache = {};\n }\n\n return isExpired;\n}\n\nfunction getExpiresAt(res: Response) {\n const cacheControlHeader = res.headers.get('cache-control');\n if (!cacheControlHeader) {\n return Date.now() + DEFAULT_CACHE_DURATION;\n }\n const maxAgeMatch = cacheControlHeader.match(CACHE_CONTROL_REGEX);\n const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : DEFAULT_CACHE_DURATION / 1000;\n\n return Date.now() + maxAge * 1000;\n}\n\nexport const getCacheStats = () => ({\n localExpiry: lastUpdatedAt + MAX_CACHE_LAST_UPDATED_AT_SECONDS * 1000,\n googleExpiry: googleExpiresAt,\n cacheCount: Object.keys(cache).length,\n});\n","import type { DecodedIdToken, TernSecureConfig, TernSecureUserData } from '@tern-secure/types';\n\nimport type { JwtReturnType } from '../jwt/types';\nimport { ternDecodeJwt, verifyJwt, type VerifyJwtOptions } from '../jwt/verifyJwt';\nimport { TokenVerificationError, TokenVerificationErrorReason } from '../utils/errors';\nimport type { LoadJWKFromRemoteOptions } from './keys';\nimport { loadJWKFromRemote } from './keys';\n\nexport type VerifyTokenVOptions = Omit<VerifyJwtOptions, 'key'> & Omit<LoadJWKFromRemoteOptions, 'kid'> & {\n jwtKey?: string;\n};\n\nexport { TernSecureConfig, TernSecureUserData };\n\nexport async function verifyToken(\n token: string,\n options: VerifyTokenVOptions,\n): Promise<JwtReturnType<DecodedIdToken, TokenVerificationError>> {\n const { data: decodedResult, errors } = ternDecodeJwt(token);\n\n if (errors) {\n return { errors };\n }\n\n const { header } = decodedResult;\n const { kid } = header;\n\n if (!kid) {\n return {\n errors: [\n new TokenVerificationError({\n reason: TokenVerificationErrorReason.TokenInvalid,\n message: 'JWT \"kid\" header is missing.',\n }),\n ],\n };\n }\n\n try {\n const key = options.jwtKey || (await loadJWKFromRemote({ ...options, kid }));\n\n if (!key) {\n return {\n errors: [\n new TokenVerificationError({\n reason: TokenVerificationErrorReason.TokenInvalid,\n message: `No public key found for kid \"${kid}\".`,\n }),\n ],\n };\n }\n return await verifyJwt(token, { ...options, key });\n } catch (error) {\n if (error instanceof TokenVerificationError) {\n return { errors: [error] };\n }\n return {\n errors: [error as TokenVerificationError],\n };\n }\n}\n","import { createCustomToken } from '../jwt/customJwt';\nimport type { AuthenticateRequestOptions, TernSecureUserData } from '../tokens/types';\nimport { verifyToken } from '../tokens/verify';\n\nexport interface IdAndRefreshTokens {\n idToken: string;\n refreshToken: string;\n}\n\nexport interface CustomTokens {\n auth_time: number;\n idToken: string;\n refreshToken: string;\n customToken: string;\n}\n\ninterface CustomForIdAndRefreshTokenOptions {\n tenantId?: string;\n appCheckToken?: string;\n referer?: string;\n}\n\ninterface FirebaseRefreshTokenResponse {\n kind: string;\n id_token: string;\n refresh_token: string;\n expires_in: string;\n isNewUser: boolean;\n}\n\ninterface FirebaseCustomTokenResponse {\n kind: string;\n idToken: string;\n refreshToken: string;\n expiresIn: string;\n isNewUser: boolean;\n}\n\ntype AuthResult<T = any> = { data: T; error: null } | { data: null; error: any };\n\nconst API_KEY_ERROR = 'API Key is required';\nconst NO_DATA_ERROR = 'No token data received';\n\nfunction parseFirebaseResponse<T>(data: unknown): T {\n if (typeof data === 'string') {\n try {\n return JSON.parse(data) as T;\n } catch (error) {\n throw new Error(`Failed to parse Firebase response: ${error}`);\n }\n }\n return data as T;\n}\n\nexport function getAuth(options: AuthenticateRequestOptions) {\n const { apiKey } = options;\n const firebaseApiKey = options.firebaseConfig?.apiKey;\n const effectiveApiKey = apiKey || firebaseApiKey;\n\n async function getUserData(idToken?: string, localId?: string): Promise<TernSecureUserData> {\n if (!effectiveApiKey) {\n throw new Error(API_KEY_ERROR);\n }\n const response = await options.apiClient?.userData.getUserData(effectiveApiKey, {\n idToken,\n localId,\n });\n\n if (!response?.data) {\n throw new Error(NO_DATA_ERROR);\n }\n\n const parsedData = parseFirebaseResponse<TernSecureUserData>(response.data);\n return parsedData;\n }\n\n async function refreshExpiredIdToken(\n refreshToken: string,\n opts: CustomForIdAndRefreshTokenOptions,\n ): Promise<AuthResult> {\n if (!effectiveApiKey) {\n return { data: null, error: new Error(API_KEY_ERROR) };\n }\n const response = await options.apiClient?.tokens.refreshToken(effectiveApiKey, {\n refresh_token: refreshToken,\n request_origin: opts.referer,\n });\n\n if (!response?.data) {\n return {\n data: null,\n error: new Error(NO_DATA_ERROR),\n };\n }\n\n const parsedData = parseFirebaseResponse<FirebaseRefreshTokenResponse>(response.data);\n\n return {\n data: {\n idToken: parsedData.id_token,\n refreshToken: parsedData.refresh_token,\n },\n error: null,\n };\n }\n\n async function customForIdAndRefreshToken(\n customToken: string,\n opts: CustomForIdAndRefreshTokenOptions,\n ): Promise<IdAndRefreshTokens> {\n if (!effectiveApiKey) {\n throw new Error('API Key is required to create custom token');\n }\n const response = await options.apiClient?.tokens.exchangeCustomForIdAndRefreshTokens(\n effectiveApiKey,\n {\n token: customToken,\n returnSecureToken: true,\n },\n {\n referer: opts.referer,\n },\n );\n\n if (!response?.data) {\n throw new Error('No data received from Firebase token exchange');\n }\n\n const parsedData = parseFirebaseResponse<FirebaseCustomTokenResponse>(response.data);\n\n return {\n idToken: parsedData.idToken,\n refreshToken: parsedData.refreshToken,\n };\n }\n\n async function createCustomIdAndRefreshToken(\n idToken: string,\n opts: CustomForIdAndRefreshTokenOptions,\n ): Promise<CustomTokens> {\n const decoded = await verifyToken(idToken, options);\n const { data, errors } = decoded;\n if (errors) {\n throw errors[0];\n }\n\n //todo:\n /**\n * For sensitive applications, the auth_time should be checked before issuing the session cookie, minimizing the window of attack in case an ID token is stolen:\n */\n //if (new Date().getTime() / 1000 - data.auth_time < 5 * 60) {\n //proceed\n //}\n\n const customToken = await createCustomToken(data.uid, {\n emailVerified: data.email_verified,\n source_sign_in_provider: data.firebase.sign_in_provider,\n });\n\n const idAndRefreshTokens = await customForIdAndRefreshToken(customToken, {\n referer: opts.referer,\n });\n\n const decodedCustomIdToken = await verifyToken(idAndRefreshTokens.idToken, options);\n if (decodedCustomIdToken.errors) {\n throw decodedCustomIdToken.errors[0];\n }\n\n return {\n ...idAndRefreshTokens,\n customToken,\n auth_time: decodedCustomIdToken.data.auth_time,\n };\n }\n\n return {\n getUserData,\n customForIdAndRefreshToken,\n createCustomIdAndRefreshToken,\n refreshExpiredIdToken,\n };\n}\n"],"mappings":"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","names":["cert"]}
@@ -17,10 +17,11 @@ export declare const constants: {
17
17
  };
18
18
  readonly Cookies: {
19
19
  readonly Session: "__session";
20
- readonly CsrfToken: "__session_terncf";
20
+ readonly CsrfToken: "__terncf";
21
21
  readonly IdToken: "TernSecure_[DEFAULT]";
22
22
  readonly Refresh: "TernSecureID_[DEFAULT]";
23
23
  readonly Custom: "__custom";
24
+ readonly TernAut: "tern_aut";
24
25
  readonly Handshake: "__ternsecure_handshake";
25
26
  readonly DevBrowser: "__ternsecure_db_jwt";
26
27
  readonly RedirectCount: "__ternsecure_redirect_count";
@@ -58,6 +59,17 @@ export declare const constants: {
58
59
  readonly ContentTypes: {
59
60
  readonly Json: "application/json";
60
61
  };
62
+ readonly QueryParameters: {
63
+ readonly TernSynced: "__tern_synced";
64
+ readonly SuffixedCookies: "suffixed_cookies";
65
+ readonly TernRedirectUrl: "__tern_redirect_url";
66
+ readonly DevBrowser: "__ternsecure_db_jwt";
67
+ readonly Handshake: "__ternsecure_handshake";
68
+ readonly HandshakeHelp: "__tern_help";
69
+ readonly LegacyDevBrowser: "__dev_session";
70
+ readonly HandshakeReason: "__tern_hs_reason";
71
+ readonly HandshakeNonce: "__ternsecure_handshake_nonce";
72
+ };
61
73
  };
62
74
  export type Constants = typeof constants;
63
75
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,6FACyD,CAAC;AAC7F,eAAO,MAAM,8BAA8B,0EAC8B,CAAC;AAE1E,eAAO,MAAM,iCAAiC,QAAS,CAAC;AACxD,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAClD,eAAO,MAAM,mBAAmB,QAAkB,CAAC;AAyDnD;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,6FACyD,CAAC;AAC7F,eAAO,MAAM,8BAA8B,0EAC8B,CAAC;AAE1E,eAAO,MAAM,iCAAiC,QAAS,CAAC;AACxD,eAAO,MAAM,sBAAsB,QAAc,CAAC;AAClD,eAAO,MAAM,mBAAmB,QAAkB,CAAC;AAwEnD;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -54,15 +54,28 @@ var Attributes = {
54
54
  };
55
55
  var Cookies = {
56
56
  Session: "__session",
57
- CsrfToken: "__session_terncf",
57
+ CsrfToken: "__terncf",
58
58
  IdToken: "TernSecure_[DEFAULT]",
59
59
  Refresh: "TernSecureID_[DEFAULT]",
60
60
  Custom: "__custom",
61
+ TernAut: "tern_aut",
61
62
  Handshake: "__ternsecure_handshake",
62
63
  DevBrowser: "__ternsecure_db_jwt",
63
64
  RedirectCount: "__ternsecure_redirect_count",
64
65
  HandshakeNonce: "__ternsecure_handshake_nonce"
65
66
  };
67
+ var QueryParameters = {
68
+ TernSynced: "__tern_synced",
69
+ SuffixedCookies: "suffixed_cookies",
70
+ TernRedirectUrl: "__tern_redirect_url",
71
+ // use the reference to Cookies to indicate that it's the same value
72
+ DevBrowser: Cookies.DevBrowser,
73
+ Handshake: Cookies.Handshake,
74
+ HandshakeHelp: "__tern_help",
75
+ LegacyDevBrowser: "__dev_session",
76
+ HandshakeReason: "__tern_hs_reason",
77
+ HandshakeNonce: Cookies.HandshakeNonce
78
+ };
66
79
  var Headers2 = {
67
80
  Accept: "accept",
68
81
  AuthMessage: "x-ternsecure-auth-message",
@@ -99,7 +112,8 @@ var constants = {
99
112
  Attributes,
100
113
  Cookies,
101
114
  Headers: Headers2,
102
- ContentTypes
115
+ ContentTypes,
116
+ QueryParameters
103
117
  };
104
118
 
105
119
  // src/createRedirect.ts
@@ -233,16 +247,20 @@ function mapJwtPayloadToDecodedIdToken(payload) {
233
247
  // src/tokens/authstate.ts
234
248
  var AuthStatus = {
235
249
  SignedIn: "signed-in",
236
- SignedOut: "signed-out"
250
+ SignedOut: "signed-out",
251
+ Handshake: "handshake"
237
252
  };
238
253
  var AuthErrorReason = {
239
- SessionTokenAndUATMissing: "session-token-and-uat-missing",
254
+ AuthTimeout: "auth-timeout",
255
+ SessionTokenAndAuthMissing: "session-token-and-aut-missing",
240
256
  SessionTokenMissing: "session-token-missing",
241
257
  SessionTokenExpired: "session-token-expired",
242
- SessionTokenIATBeforeClientUAT: "session-token-iat-before-client-uat",
258
+ SessionTokenIATBeforeTernAUT: "session-token-iat-before-tern-aut",
243
259
  SessionTokenNBF: "session-token-nbf",
244
260
  SessionTokenIatInTheFuture: "session-token-iat-in-the-future",
245
- ActiveOrganizationMismatch: "active-organization-mismatch",
261
+ SessionTokenWithoutTernAUT: "session-token-but-no-tern-uat",
262
+ TernAutWithoutSessionToken: "tern-aut-but-no-session-token",
263
+ SyncRequired: "sync-required",
246
264
  UnexpectedError: "unexpected-error"
247
265
  };
248
266
  function createHasAuthorization(decodedIdToken) {
@@ -294,6 +312,7 @@ function signedIn(authCtx, sessionClaims, headers = new Headers(), token) {
294
312
  const authObject = signedInAuthObject(token, sessionClaims);
295
313
  return {
296
314
  status: AuthStatus.SignedIn,
315
+ message: null,
297
316
  reason: null,
298
317
  signInUrl: authCtx.signInUrl || "",
299
318
  signUpUrl: authCtx.signUpUrl || "",
@@ -318,6 +337,12 @@ function signedOut(authCtx, reason, message = "", headers = new Headers()) {
318
337
  }
319
338
  var decorateHeaders = (requestState) => {
320
339
  const headers = new Headers(requestState.headers || {});
340
+ if (requestState.message) {
341
+ try {
342
+ headers.set(constants.Headers.AuthMessage, requestState.message);
343
+ } catch {
344
+ }
345
+ }
321
346
  if (requestState.reason) {
322
347
  try {
323
348
  headers.set(constants.Headers.AuthReason, requestState.reason);
@@ -705,6 +730,9 @@ function mergePreDefinedOptions(userOptions = {}) {
705
730
  };
706
731
  }
707
732
 
733
+ // src/tokens/request.ts
734
+ var import_ms = require("@tern-secure/shared/ms");
735
+
708
736
  // src/jwt/customJwt.ts
709
737
  var import_jose = require("jose");
710
738
  var CustomTokenError = class extends Error {
@@ -1041,44 +1069,33 @@ async function verifySignature(jwt, key) {
1041
1069
  }
1042
1070
  }
1043
1071
  function ternDecodeJwt(token) {
1044
- try {
1045
- const header = (0, import_jose3.decodeProtectedHeader)(token);
1046
- const payload = (0, import_jose3.decodeJwt)(token);
1047
- const tokenParts = (token || "").toString().split(".");
1048
- if (tokenParts.length !== 3) {
1049
- return {
1050
- errors: [
1051
- new TokenVerificationError({
1052
- reason: TokenVerificationErrorReason.TokenInvalid,
1053
- message: "Invalid JWT format"
1054
- })
1055
- ]
1056
- };
1057
- }
1058
- const [rawHeader, rawPayload, rawSignature] = tokenParts;
1059
- const signature = base64url.parse(rawSignature, { loose: true });
1060
- const data = {
1061
- header,
1062
- payload,
1063
- signature,
1064
- raw: {
1065
- header: rawHeader,
1066
- payload: rawPayload,
1067
- signature: rawSignature,
1068
- text: token
1069
- }
1070
- };
1071
- return { data };
1072
- } catch (error) {
1072
+ const header = (0, import_jose3.decodeProtectedHeader)(token);
1073
+ const payload = (0, import_jose3.decodeJwt)(token);
1074
+ const tokenParts = (token || "").toString().split(".");
1075
+ if (tokenParts.length !== 3) {
1073
1076
  return {
1074
1077
  errors: [
1075
1078
  new TokenVerificationError({
1076
1079
  reason: TokenVerificationErrorReason.TokenInvalid,
1077
- message: error.message
1080
+ message: "Invalid JWT format"
1078
1081
  })
1079
1082
  ]
1080
1083
  };
1081
1084
  }
1085
+ const [rawHeader, rawPayload, rawSignature] = tokenParts;
1086
+ const signature = base64url.parse(rawSignature, { loose: true });
1087
+ const data = {
1088
+ header,
1089
+ payload,
1090
+ signature,
1091
+ raw: {
1092
+ header: rawHeader,
1093
+ payload: rawPayload,
1094
+ signature: rawSignature,
1095
+ text: token
1096
+ }
1097
+ };
1098
+ return { data };
1082
1099
  }
1083
1100
  async function verifyJwt(token, options) {
1084
1101
  const { key } = options;
@@ -1326,9 +1343,14 @@ function getAuth(options) {
1326
1343
  const idAndRefreshTokens = await customForIdAndRefreshToken(customToken, {
1327
1344
  referer: opts.referer
1328
1345
  });
1346
+ const decodedCustomIdToken = await verifyToken(idAndRefreshTokens.idToken, options);
1347
+ if (decodedCustomIdToken.errors) {
1348
+ throw decodedCustomIdToken.errors[0];
1349
+ }
1329
1350
  return {
1330
1351
  ...idAndRefreshTokens,
1331
- customToken
1352
+ customToken,
1353
+ auth_time: decodedCustomIdToken.data.auth_time
1332
1354
  };
1333
1355
  }
1334
1356
  return {
@@ -1346,6 +1368,7 @@ var RequestProcessorContext = class {
1346
1368
  this.options = options;
1347
1369
  this.initHeaderValues();
1348
1370
  this.initCookieValues();
1371
+ this.initHandshakeValues();
1349
1372
  this.initUrlValues();
1350
1373
  Object.assign(this, options);
1351
1374
  this.ternUrl = this.ternSecureRequest.ternUrl;
@@ -1374,6 +1397,11 @@ var RequestProcessorContext = class {
1374
1397
  this.refreshTokenInCookie = this.getCookie(`${defaultPrefix}${constants.Cookies.Refresh}`);
1375
1398
  this.csrfTokenInCookie = this.getCookie(constants.Cookies.CsrfToken);
1376
1399
  this.customTokenInCookie = this.getCookie(constants.Cookies.Custom);
1400
+ this.ternAuth = Number.parseInt(this.getCookie(constants.Cookies.TernAut) || "0", 10);
1401
+ }
1402
+ initHandshakeValues() {
1403
+ this.handshakeToken = this.getQueryParam(constants.QueryParameters.Handshake) || this.getCookie(constants.Cookies.Handshake);
1404
+ this.handshakeNonce = this.getQueryParam(constants.QueryParameters.HandshakeNonce) || this.getCookie(constants.Cookies.HandshakeNonce);
1377
1405
  }
1378
1406
  initUrlValues() {
1379
1407
  this.method = this.ternSecureRequest.method;
@@ -1381,6 +1409,9 @@ var RequestProcessorContext = class {
1381
1409
  this.endpoint = this.pathSegments[2];
1382
1410
  this.subEndpoint = this.pathSegments[3];
1383
1411
  }
1412
+ getQueryParam(name) {
1413
+ return this.ternSecureRequest.ternUrl.searchParams.get(name);
1414
+ }
1384
1415
  getHeader(name) {
1385
1416
  return this.ternSecureRequest.headers.get(name) || void 0;
1386
1417
  }
@@ -1412,6 +1443,9 @@ var import_cookie2 = require("@tern-secure/shared/cookie");
1412
1443
  function hasAuthorizationHeader(request) {
1413
1444
  return request.headers.has("Authorization");
1414
1445
  }
1446
+ function convertToSeconds(value) {
1447
+ return (0, import_ms.ms)(value) / 1e3;
1448
+ }
1415
1449
  function isRequestForRefresh(error, context, request) {
1416
1450
  return error.reason === TokenVerificationErrorReason.TokenExpired && !!context.refreshTokenInCookie && request.method === "GET";
1417
1451
  }
@@ -1419,6 +1453,17 @@ async function authenticateRequest(request, options) {
1419
1453
  const context = createRequestProcessor(createTernSecureRequest(request), options);
1420
1454
  const { refreshTokenInCookie } = context;
1421
1455
  const { refreshExpiredIdToken } = getAuth(options);
1456
+ function checkSessionTimeout(authTimeValue) {
1457
+ const defaultMaxAgeSeconds = convertToSeconds("5 days");
1458
+ const REAUTH_PERIOD_SECONDS = context.session?.maxAge ? convertToSeconds(context.session.maxAge) : defaultMaxAgeSeconds;
1459
+ const currentTime = Math.floor(Date.now() / 1e3);
1460
+ const authAge = currentTime - authTimeValue;
1461
+ console.log("Current time:", currentTime, "Auth age:", authAge, "Reauth period (s):", REAUTH_PERIOD_SECONDS);
1462
+ if (authTimeValue > 0 && authAge > REAUTH_PERIOD_SECONDS) {
1463
+ return signedOut(context, AuthErrorReason.AuthTimeout, "Authentication expired");
1464
+ }
1465
+ return null;
1466
+ }
1422
1467
  async function refreshToken() {
1423
1468
  if (!refreshTokenInCookie) {
1424
1469
  return {
@@ -1440,10 +1485,10 @@ async function authenticateRequest(request, options) {
1440
1485
  }
1441
1486
  const headers = new Headers();
1442
1487
  const { idToken } = refreshedData;
1443
- const maxAge = 3600;
1488
+ const maxAge = 365 * 24 * 60 * 60;
1444
1489
  const cookiePrefix = (0, import_cookie2.getCookiePrefix)();
1445
1490
  const idTokenCookieName = (0, import_cookie2.getCookieName)(constants.Cookies.IdToken, cookiePrefix);
1446
- const baseCookieAttributes = "HttpOnly; Secure; SameSite=Strict; Path=/";
1491
+ const baseCookieAttributes = `HttpOnly; Secure; SameSite=Strict; Max-Age=${maxAge}; Path=/`;
1447
1492
  const idTokenCookie = `${idTokenCookieName}=${idToken}; ${baseCookieAttributes};`;
1448
1493
  headers.append("Set-Cookie", idTokenCookie);
1449
1494
  const { data: decoded, errors } = await verifyToken(idToken, options);
@@ -1455,7 +1500,78 @@ async function authenticateRequest(request, options) {
1455
1500
  }
1456
1501
  return { data: { decoded, token: idToken, headers }, error: null };
1457
1502
  }
1503
+ async function handleLocalHandshakeWithErrorCheck(context2, reason, message, skipSessionCheck = false) {
1504
+ const hasRefreshTokenInCookie = !!context2.refreshTokenInCookie;
1505
+ if (!hasRefreshTokenInCookie) {
1506
+ return signedOut(context2, reason, "Refresh token missing in cookie");
1507
+ }
1508
+ if (reason === AuthErrorReason.TernAutWithoutSessionToken) {
1509
+ if (!skipSessionCheck) {
1510
+ const sessionTimeoutResult = checkSessionTimeout(context2.ternAuth);
1511
+ if (sessionTimeoutResult) {
1512
+ return sessionTimeoutResult;
1513
+ }
1514
+ }
1515
+ const { data, error } = await handleRefresh();
1516
+ if (data) {
1517
+ return signedIn(context2, data.decoded, data.headers, data.token);
1518
+ }
1519
+ return signedOut(context2, reason, "Failed to refresh idToken");
1520
+ }
1521
+ if (reason === AuthErrorReason.SessionTokenWithoutTernAUT || reason === AuthErrorReason.SessionTokenIATBeforeTernAUT) {
1522
+ const { data, errors } = ternDecodeJwt(context2.idTokenInCookie);
1523
+ if (errors) {
1524
+ throw errors[0];
1525
+ }
1526
+ const authTime = data.payload.auth_time;
1527
+ if (!authTime || typeof authTime !== "number") {
1528
+ return signedOut(context2, reason, "Token missing auth_time");
1529
+ }
1530
+ if (!skipSessionCheck) {
1531
+ const sessionTimeoutResult = checkSessionTimeout(authTime);
1532
+ if (sessionTimeoutResult) {
1533
+ return sessionTimeoutResult;
1534
+ }
1535
+ }
1536
+ const { data: verifiedToken, errors: verifyErrors } = await verifyToken(context2.idTokenInCookie, options);
1537
+ if (verifyErrors) {
1538
+ throw verifyErrors[0];
1539
+ }
1540
+ const headers = new Headers();
1541
+ const oneYearInSeconds = 365 * 24 * 60 * 60;
1542
+ const ternAutCookie = `${constants.Cookies.TernAut}=${authTime}; Max-Age=${oneYearInSeconds}; Secure; SameSite=Strict; Path=/`;
1543
+ headers.append("Set-Cookie", ternAutCookie);
1544
+ return signedIn(context2, verifiedToken, headers, context2.idTokenInCookie);
1545
+ }
1546
+ return signedOut(context2, reason, message);
1547
+ }
1458
1548
  async function authenticateRequestWithTokenInCookie() {
1549
+ const hasTernAuth = context.ternAuth;
1550
+ const hasIdTokenInCookie = !!context.idTokenInCookie;
1551
+ if (!hasTernAuth && !hasIdTokenInCookie) {
1552
+ return signedOut(context, AuthErrorReason.SessionTokenAndAuthMissing);
1553
+ }
1554
+ if (!hasTernAuth && hasIdTokenInCookie) {
1555
+ return await handleLocalHandshakeWithErrorCheck(context, AuthErrorReason.SessionTokenWithoutTernAUT, "");
1556
+ }
1557
+ if (hasTernAuth && !hasIdTokenInCookie) {
1558
+ return await handleLocalHandshakeWithErrorCheck(context, AuthErrorReason.TernAutWithoutSessionToken, "");
1559
+ }
1560
+ const sessionTimeoutResult = checkSessionTimeout(context.ternAuth);
1561
+ if (sessionTimeoutResult) {
1562
+ return sessionTimeoutResult;
1563
+ }
1564
+ const { data: decodedResult, errors: decodeErrors } = ternDecodeJwt(context.idTokenInCookie);
1565
+ if (decodeErrors) {
1566
+ return handleError(decodeErrors[0], "cookie");
1567
+ }
1568
+ const tokenIat = decodedResult.payload.iat;
1569
+ if (!tokenIat) {
1570
+ return signedOut(context, AuthErrorReason.SessionTokenMissing, "");
1571
+ }
1572
+ if (tokenIat < context.ternAuth) {
1573
+ return await handleLocalHandshakeWithErrorCheck(context, AuthErrorReason.SessionTokenIATBeforeTernAUT, "", true);
1574
+ }
1459
1575
  try {
1460
1576
  const { data, errors } = await verifyToken(context.idTokenInCookie, options);
1461
1577
  if (errors) {
@@ -1466,6 +1582,7 @@ async function authenticateRequest(request, options) {
1466
1582
  } catch (err) {
1467
1583
  return handleError(err, "cookie");
1468
1584
  }
1585
+ return signedOut(context, AuthErrorReason.UnexpectedError);
1469
1586
  }
1470
1587
  async function authenticateRequestWithTokenInHeader() {
1471
1588
  const { sessionTokenInHeader } = context;