@absolutejs/auth 0.27.0-beta.4 → 0.27.0-beta.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.
@@ -0,0 +1,9 @@
1
+ export type EmailValidationResult = {
2
+ ok: boolean;
3
+ reason?: 'disposable' | 'invalid_format' | 'no_mx';
4
+ };
5
+ export declare const isDisposableEmail: (email: string, extraDomains?: Iterable<string>) => boolean;
6
+ export declare const validateEmailDeliverability: (email: string, options?: {
7
+ checkMx?: boolean;
8
+ disposableDomains?: Iterable<string>;
9
+ }) => Promise<EmailValidationResult>;
@@ -12,3 +12,4 @@ export type PasswordPolicyResult = {
12
12
  violations: PasswordPolicyViolation[];
13
13
  };
14
14
  export declare const evaluatePassword: (password: string, policy?: PasswordPolicy) => Promise<PasswordPolicyResult>;
15
+ export declare const isPasswordCompromised: (password: string) => Promise<boolean>;
package/dist/index.d.ts CHANGED
@@ -13477,6 +13477,8 @@ export declare const auth: <UserType>({ providersConfiguration, authorizeRoute,
13477
13477
  [x: string]: {
13478
13478
  post: {
13479
13479
  body: {
13480
+ audience?: string | undefined;
13481
+ resource?: string | undefined;
13480
13482
  client_id?: string | undefined;
13481
13483
  scope?: string | undefined;
13482
13484
  refresh_token?: string | undefined;
@@ -13485,6 +13487,8 @@ export declare const auth: <UserType>({ providersConfiguration, authorizeRoute,
13485
13487
  code?: string | undefined;
13486
13488
  redirect_uri?: string | undefined;
13487
13489
  code_verifier?: string | undefined;
13490
+ subject_token?: string | undefined;
13491
+ subject_token_type?: string | undefined;
13488
13492
  };
13489
13493
  params: {};
13490
13494
  query: unknown;
@@ -14648,6 +14652,8 @@ export { sessionRoutes } from './routes/sessions';
14648
14652
  export { stepUpPlugin } from './routes/stepUp';
14649
14653
  export * from './session/sessionsConfig';
14650
14654
  export { endImpersonation, isImpersonating, startImpersonation } from './session/impersonation';
14655
+ export { createAnonymousSession, isAnonymousSession } from './session/anonymous';
14656
+ export { addToSessionRing, listRingSessions, readSessionRing, removeFromSessionRing, switchActiveSession } from './session/multiSession';
14651
14657
  export { listUserSessions, revokeUserSessions } from './session/userSessions';
14652
14658
  export type { UserSession } from './session/userSessions';
14653
14659
  export { sessionCleanup } from './session/cleanup';
@@ -14662,6 +14668,7 @@ export * from './crypto';
14662
14668
  export * from './tenancy';
14663
14669
  export * from './credentials/config';
14664
14670
  export * from './credentials/passwordPolicy';
14671
+ export * from './credentials/emailValidation';
14665
14672
  export * from './credentials/types';
14666
14673
  export { credentialRoutes } from './credentials/routes';
14667
14674
  export { credentialsEmailVerification } from './credentials/emailVerification';
package/dist/index.js CHANGED
@@ -3213,6 +3213,7 @@ var persistWhen = async (shouldPersist, persist) => {
3213
3213
  await persist();
3214
3214
  };
3215
3215
  var promoteToSession = async ({
3216
+ anonymous,
3216
3217
  authSessionStore,
3217
3218
  cookie,
3218
3219
  impersonator,
@@ -3236,6 +3237,8 @@ var promoteToSession = async ({
3236
3237
  data.samlLogout = samlLogout;
3237
3238
  if (impersonator !== undefined)
3238
3239
  data.impersonator = impersonator;
3240
+ if (anonymous === true)
3241
+ data.anonymous = true;
3239
3242
  targetSession[userSessionId] = data;
3240
3243
  cookie.set({
3241
3244
  httpOnly: true,
@@ -3512,6 +3515,7 @@ var evaluatePassword = async (password, policy = {}) => {
3512
3515
  }
3513
3516
  return { ok: violations.length === 0, violations };
3514
3517
  };
3518
+ var isPasswordCompromised = (password) => isPasswordBreached(password);
3515
3519
 
3516
3520
  // src/credentials/passwordReset.ts
3517
3521
  var credentialsPasswordReset = ({
@@ -4134,6 +4138,72 @@ var DEFAULT_ACCESS_TOKEN_TTL_MS2 = MILLISECONDS_IN_AN_HOUR;
4134
4138
  var DEFAULT_ID_TOKEN_TTL_MS = MILLISECONDS_IN_AN_HOUR;
4135
4139
  var DEFAULT_REFRESH_TOKEN_TTL_MS = MILLISECONDS_IN_A_DAY * REFRESH_TTL_DAYS;
4136
4140
  var nowSeconds = (milliseconds) => Math.floor(milliseconds / MS_PER_SECOND);
4141
+ var narrowScopes = (available, requested) => requested === undefined || requested.length === 0 ? available : requested.filter((scope) => available.includes(scope));
4142
+ var buildAccessClaims = ({
4143
+ act,
4144
+ audience,
4145
+ clientId,
4146
+ dpopJkt,
4147
+ issuer,
4148
+ now,
4149
+ scopes,
4150
+ sub,
4151
+ ttl
4152
+ }) => {
4153
+ const claims = {
4154
+ aud: audience ?? clientId,
4155
+ client_id: clientId,
4156
+ exp: nowSeconds(now + ttl),
4157
+ iat: nowSeconds(now),
4158
+ iss: issuer,
4159
+ jti: crypto.randomUUID(),
4160
+ scope: scopes.join(" "),
4161
+ sub,
4162
+ token_use: "access"
4163
+ };
4164
+ if (act !== undefined)
4165
+ claims.act = act;
4166
+ if (dpopJkt !== undefined)
4167
+ claims.cnf = { jkt: dpopJkt };
4168
+ return claims;
4169
+ };
4170
+ var exchangeToken = async ({
4171
+ actorClientId,
4172
+ audience,
4173
+ config,
4174
+ dpopJkt,
4175
+ now = Date.now(),
4176
+ requestedScopes,
4177
+ subjectToken
4178
+ }) => {
4179
+ const verified = await verifyJwt(subjectToken, config.signingKey.publicJwk);
4180
+ const payload = verified?.payload;
4181
+ if (payload === undefined || typeof payload.sub !== "string" || typeof payload.exp !== "number" || payload.exp <= nowSeconds(now)) {
4182
+ return { error: "invalid_grant", ok: false };
4183
+ }
4184
+ const available = typeof payload.scope === "string" ? payload.scope.split(" ") : [];
4185
+ if (requestedScopes?.some((scope) => !available.includes(scope)) === true) {
4186
+ return { error: "invalid_scope", ok: false };
4187
+ }
4188
+ const scopes = narrowScopes(available, requestedScopes);
4189
+ const ttl = config.accessTokenTtlMs ?? DEFAULT_ACCESS_TOKEN_TTL_MS2;
4190
+ return {
4191
+ accessToken: await signJwt(buildAccessClaims({
4192
+ act: { sub: actorClientId },
4193
+ audience,
4194
+ clientId: actorClientId,
4195
+ dpopJkt,
4196
+ issuer: config.issuer,
4197
+ now,
4198
+ scopes,
4199
+ sub: payload.sub,
4200
+ ttl
4201
+ }), config.signingKey),
4202
+ expiresIn: Math.floor(ttl / MS_PER_SECOND),
4203
+ ok: true,
4204
+ scope: scopes.join(" ")
4205
+ };
4206
+ };
4137
4207
  var issueTokenSet = async ({
4138
4208
  claims,
4139
4209
  clientId,
@@ -4147,19 +4217,15 @@ var issueTokenSet = async ({
4147
4217
  const accessTtl = config.accessTokenTtlMs ?? DEFAULT_ACCESS_TOKEN_TTL_MS2;
4148
4218
  const idTtl = config.idTokenTtlMs ?? DEFAULT_ID_TOKEN_TTL_MS;
4149
4219
  const refreshTtl = config.refreshTokenTtlMs ?? DEFAULT_REFRESH_TOKEN_TTL_MS;
4150
- const accessPayload = {
4151
- aud: clientId,
4152
- client_id: clientId,
4153
- exp: nowSeconds(now + accessTtl),
4154
- iat: nowSeconds(now),
4155
- iss: config.issuer,
4156
- jti: crypto.randomUUID(),
4157
- scope: scopes.join(" "),
4220
+ const accessPayload = buildAccessClaims({
4221
+ clientId,
4222
+ dpopJkt,
4223
+ issuer: config.issuer,
4224
+ now,
4225
+ scopes,
4158
4226
  sub,
4159
- token_use: "access"
4160
- };
4161
- if (dpopJkt !== undefined)
4162
- accessPayload.cnf = { jkt: dpopJkt };
4227
+ ttl: accessTtl
4228
+ });
4163
4229
  const idPayload = {
4164
4230
  ...claims,
4165
4231
  aud: clientId,
@@ -4190,6 +4256,15 @@ var issueTokenSet = async ({
4190
4256
  token_type: dpopJkt === undefined ? "Bearer" : "DPoP"
4191
4257
  };
4192
4258
  };
4259
+ var mcpProtectedResourceMetadata = ({
4260
+ issuer,
4261
+ resource,
4262
+ scopes
4263
+ }) => ({
4264
+ authorization_servers: [issuer],
4265
+ resource,
4266
+ scopes_supported: scopes ?? []
4267
+ });
4193
4268
  var verifyPkce = async (codeVerifier, codeChallenge) => await hashToken(codeVerifier) === codeChallenge;
4194
4269
 
4195
4270
  // src/oidc/dpop.ts
@@ -4356,11 +4431,45 @@ var oidcProviderRoutes = (config) => {
4356
4431
  sub: record.userId
4357
4432
  }));
4358
4433
  };
4434
+ const grantTokenExchange = async (client, body, dpop) => {
4435
+ if (body.subject_token === undefined) {
4436
+ return oauthError2(HTTP_BAD_REQUEST2, "invalid_request");
4437
+ }
4438
+ const dpopResult = dpop === undefined ? undefined : await verifyDpopProof({
4439
+ htm: "POST",
4440
+ htu: tokenUrl,
4441
+ proof: dpop
4442
+ });
4443
+ if (dpop !== undefined && dpopResult === undefined) {
4444
+ return oauthError2(HTTP_BAD_REQUEST2, "invalid_dpop_proof");
4445
+ }
4446
+ const result = await exchangeToken({
4447
+ actorClientId: client.clientId,
4448
+ audience: body.resource ?? body.audience,
4449
+ config,
4450
+ dpopJkt: dpopResult?.jkt,
4451
+ requestedScopes: body.scope === undefined || body.scope.length === 0 ? undefined : body.scope.split(" "),
4452
+ subjectToken: body.subject_token
4453
+ });
4454
+ if (!result.ok)
4455
+ return oauthError2(HTTP_BAD_REQUEST2, result.error);
4456
+ return jsonResponse({
4457
+ access_token: result.accessToken,
4458
+ expires_in: result.expiresIn,
4459
+ issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
4460
+ scope: result.scope,
4461
+ token_type: dpopResult === undefined ? "Bearer" : "DPoP"
4462
+ }, HTTP_OK2);
4463
+ };
4359
4464
  const discovery = {
4360
4465
  authorization_endpoint: `${issuer}${authorizeRoute}`,
4361
4466
  code_challenge_methods_supported: ["S256"],
4362
4467
  dpop_signing_alg_values_supported: ["ES256"],
4363
- grant_types_supported: ["authorization_code", "refresh_token"],
4468
+ grant_types_supported: [
4469
+ "authorization_code",
4470
+ "refresh_token",
4471
+ "urn:ietf:params:oauth:grant-type:token-exchange"
4472
+ ],
4364
4473
  id_token_signing_alg_values_supported: ["ES256"],
4365
4474
  issuer,
4366
4475
  jwks_uri: `${issuer}${jwksRoute}`,
@@ -4467,9 +4576,13 @@ var oidcProviderRoutes = (config) => {
4467
4576
  if (body.grant_type === "refresh_token") {
4468
4577
  return grantRefreshToken(client, body, headers.dpop);
4469
4578
  }
4579
+ if (body.grant_type === "urn:ietf:params:oauth:grant-type:token-exchange") {
4580
+ return grantTokenExchange(client, body, headers.dpop);
4581
+ }
4470
4582
  return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
4471
4583
  }, {
4472
4584
  body: t12.Object({
4585
+ audience: t12.Optional(t12.String()),
4473
4586
  client_id: t12.Optional(t12.String()),
4474
4587
  client_secret: t12.Optional(t12.String()),
4475
4588
  code: t12.Optional(t12.String()),
@@ -4477,7 +4590,10 @@ var oidcProviderRoutes = (config) => {
4477
4590
  grant_type: t12.Optional(t12.String()),
4478
4591
  redirect_uri: t12.Optional(t12.String()),
4479
4592
  refresh_token: t12.Optional(t12.String()),
4480
- scope: t12.Optional(t12.String())
4593
+ resource: t12.Optional(t12.String()),
4594
+ scope: t12.Optional(t12.String()),
4595
+ subject_token: t12.Optional(t12.String()),
4596
+ subject_token_type: t12.Optional(t12.String())
4481
4597
  })
4482
4598
  }).get(jwksRoute, () => ({ keys: [toPublicJwk(signingKey)] })).get("/.well-known/openid-configuration", () => discovery);
4483
4599
  };
@@ -18029,6 +18145,90 @@ var startImpersonation = async ({
18029
18145
  });
18030
18146
  return sessionId;
18031
18147
  };
18148
+ // src/session/anonymous.ts
18149
+ var DEFAULT_GUEST_TTL_MS = MILLISECONDS_IN_A_DAY;
18150
+ var createAnonymousSession = async ({
18151
+ authSessionStore,
18152
+ cookie,
18153
+ guestUser,
18154
+ inMemorySession,
18155
+ sessionDurationMs = DEFAULT_GUEST_TTL_MS
18156
+ }) => promoteToSession({
18157
+ anonymous: true,
18158
+ authSessionStore,
18159
+ cookie,
18160
+ inMemorySession,
18161
+ sessionDurationMs,
18162
+ user: guestUser
18163
+ });
18164
+ var isAnonymousSession = (session) => session?.anonymous === true;
18165
+ // src/session/multiSession.ts
18166
+ var SEPARATOR = " ";
18167
+ var writeRing = (ring, ids) => ring.set({
18168
+ httpOnly: true,
18169
+ sameSite: "lax",
18170
+ secure: true,
18171
+ value: ids.join(SEPARATOR)
18172
+ });
18173
+ var readRing = (ring) => (ring.value ?? "").split(SEPARATOR).filter((entry) => isUserSessionId(entry));
18174
+ var addToSessionRing = (ring, sessionId) => writeRing(ring, [...new Set([...readRing(ring), sessionId])]);
18175
+ var listRingSessions = async ({
18176
+ authSessionStore,
18177
+ inMemorySession,
18178
+ ring
18179
+ }) => {
18180
+ const resolved = await Promise.all(readRing(ring).map(async (sessionId) => {
18181
+ const session = await loadSessionFromSource({
18182
+ authSessionStore,
18183
+ session: inMemorySession,
18184
+ userSessionId: sessionId
18185
+ });
18186
+ return session === undefined ? undefined : { sessionId, user: session.user };
18187
+ }));
18188
+ return resolved.filter((entry) => entry !== undefined);
18189
+ };
18190
+ var readSessionRing = (ring) => readRing(ring);
18191
+ var removeFromSessionRing = async ({
18192
+ activeCookie,
18193
+ authSessionStore,
18194
+ inMemorySession,
18195
+ ring,
18196
+ sessionId
18197
+ }) => {
18198
+ const remaining = readRing(ring).filter((id) => id !== sessionId);
18199
+ writeRing(ring, remaining);
18200
+ if (authSessionStore)
18201
+ await authSessionStore.removeSession(sessionId);
18202
+ else if (inMemorySession)
18203
+ delete inMemorySession[sessionId];
18204
+ if (activeCookie?.value !== sessionId)
18205
+ return;
18206
+ const [fallback] = remaining;
18207
+ if (fallback === undefined)
18208
+ activeCookie.remove();
18209
+ else
18210
+ activeCookie.set({
18211
+ httpOnly: true,
18212
+ sameSite: "lax",
18213
+ secure: true,
18214
+ value: fallback
18215
+ });
18216
+ };
18217
+ var switchActiveSession = ({
18218
+ activeCookie,
18219
+ ring,
18220
+ sessionId
18221
+ }) => {
18222
+ if (!readRing(ring).includes(sessionId))
18223
+ return false;
18224
+ activeCookie.set({
18225
+ httpOnly: true,
18226
+ sameSite: "lax",
18227
+ secure: true,
18228
+ value: sessionId
18229
+ });
18230
+ return true;
18231
+ };
18032
18232
  // src/utils.ts
18033
18233
  var defineAuthConfig = (configuration) => configuration;
18034
18234
  var defineAuthHtmxConfig = (htmxConfig) => htmxConfig;
@@ -18204,6 +18404,48 @@ var getUserSessionId = ({
18204
18404
  };
18205
18405
  // src/tenancy.ts
18206
18406
  var hasOrganizationScope = (value) => typeof value.organizationId === "string" && value.organizationId.length > 0;
18407
+ // src/credentials/emailValidation.ts
18408
+ import { resolveMx } from "dns/promises";
18409
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
18410
+ var DISPOSABLE_DOMAINS = new Set([
18411
+ "10minutemail.com",
18412
+ "fakeinbox.com",
18413
+ "getnada.com",
18414
+ "guerrillamail.com",
18415
+ "mailinator.com",
18416
+ "maildrop.cc",
18417
+ "sharklasers.com",
18418
+ "temp-mail.org",
18419
+ "tempmail.com",
18420
+ "throwaway.email",
18421
+ "trashmail.com",
18422
+ "yopmail.com"
18423
+ ]);
18424
+ var domainOf = (email) => email.slice(email.lastIndexOf("@") + 1).toLowerCase();
18425
+ var hasMxRecord = async (domain) => {
18426
+ try {
18427
+ return (await resolveMx(domain)).length > 0;
18428
+ } catch {
18429
+ return false;
18430
+ }
18431
+ };
18432
+ var isDisposableEmail = (email, extraDomains) => {
18433
+ const domain = domainOf(email);
18434
+ return DISPOSABLE_DOMAINS.has(domain) || extraDomains !== undefined && new Set(extraDomains).has(domain);
18435
+ };
18436
+ var validateEmailDeliverability = async (email, options) => {
18437
+ const normalized = email.trim().toLowerCase();
18438
+ if (!EMAIL_PATTERN.test(normalized)) {
18439
+ return { ok: false, reason: "invalid_format" };
18440
+ }
18441
+ if (isDisposableEmail(normalized, options?.disposableDomains)) {
18442
+ return { ok: false, reason: "disposable" };
18443
+ }
18444
+ if (options?.checkMx === true && !await hasMxRecord(domainOf(normalized))) {
18445
+ return { ok: false, reason: "no_mx" };
18446
+ }
18447
+ return { ok: true };
18448
+ };
18207
18449
  // src/credentials/inMemoryCredentialStore.ts
18208
18450
  var cloneCredential = (value) => ({
18209
18451
  ...value
@@ -20468,9 +20710,11 @@ export {
20468
20710
  verifyApiKey,
20469
20711
  verifyAccessToken,
20470
20712
  validateSession,
20713
+ validateEmailDeliverability,
20471
20714
  userSessionIdTypebox,
20472
20715
  trustDevice,
20473
20716
  toPublicJwk,
20717
+ switchActiveSession,
20474
20718
  stepUpPlugin,
20475
20719
  startImpersonation,
20476
20720
  ssoDiscoveryRoute,
@@ -20500,8 +20744,10 @@ export {
20500
20744
  resolveClientProviderEntry,
20501
20745
  resolveAuthHtmxRenderers,
20502
20746
  resolveApiPrincipal,
20747
+ removeFromSessionRing,
20503
20748
  refreshableProviderOptions,
20504
20749
  recordLoginAttempt,
20750
+ readSessionRing,
20505
20751
  providers,
20506
20752
  providerOptions,
20507
20753
  protectRoutePlugin,
@@ -20524,11 +20770,13 @@ export {
20524
20770
  mfaRoutes,
20525
20771
  mfaEnrollmentsTable,
20526
20772
  mfaChallenge,
20773
+ mcpProtectedResourceMetadata,
20527
20774
  loginHistoryTable,
20528
20775
  lockoutsTable,
20529
20776
  listUserSessions,
20530
20777
  listUserOrganizations,
20531
20778
  listSubjects,
20779
+ listRingSessions,
20532
20780
  knownDevicesTable,
20533
20781
  jwkThumbprint,
20534
20782
  issueTokenSet,
@@ -20539,11 +20787,14 @@ export {
20539
20787
  isRevocableOAuth2Client,
20540
20788
  isRefreshableProviderOption,
20541
20789
  isRefreshableOAuth2Client,
20790
+ isPasswordCompromised,
20542
20791
  isPKCEProviderOption,
20543
20792
  isOIDCProviderOption,
20544
20793
  isMfaEnrolled,
20545
20794
  isImpersonating,
20795
+ isDisposableEmail,
20546
20796
  isAuthIntent,
20797
+ isAnonymousSession,
20547
20798
  inviteToOrganization,
20548
20799
  instantiateUserSession,
20549
20800
  hashToken,
@@ -20560,6 +20811,7 @@ export {
20560
20811
  generateEncryptionKey,
20561
20812
  generateBackupCodes,
20562
20813
  extractPropFromIdentity,
20814
+ exchangeToken,
20563
20815
  exchangeClientCredentials,
20564
20816
  evaluatePassword,
20565
20817
  endImpersonation,
@@ -20670,6 +20922,7 @@ export {
20670
20922
  createAuditEmitter,
20671
20923
  createApiKey,
20672
20924
  createApiClient,
20925
+ createAnonymousSession,
20673
20926
  createAbuseGuard,
20674
20927
  consumeBackupCode,
20675
20928
  constantTimeEqual,
@@ -20688,6 +20941,7 @@ export {
20688
20941
  apiKeysTable,
20689
20942
  apiKeysRoutes,
20690
20943
  apiClientsTable,
20944
+ addToSessionRing,
20691
20945
  accessTokensTable,
20692
20946
  acceptInvitation,
20693
20947
  WEBAUTHN_CHALLENGE_COOKIE,
@@ -20720,5 +20974,5 @@ export {
20720
20974
  AuthIdentityConflictError
20721
20975
  };
20722
20976
 
20723
- //# debugId=C4383EA08F57C5E064756E2164756E21
20977
+ //# debugId=D6A51261327D341C64756E2164756E21
20724
20978
  //# sourceMappingURL=index.js.map