@absolutejs/auth 0.65.4 → 0.65.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.
@@ -69,9 +69,10 @@ export type CredentialsConfig<UserType> = {
69
69
  }) => void | Promise<void>;
70
70
  passwordPolicy?: PasswordPolicy;
71
71
  registerRoute?: RouteString;
72
- /** When true, registration creates the account but NOT a session, and login is
73
- * rejected until the email is verified. Default false = auto-login on register and
74
- * verification acts as a soft, later gate. */
72
+ /** When true, registration stores only a pending credential and does NOT call
73
+ * `onCreateCredentialUser` until a verification token is accepted. No user
74
+ * account or session exists before email ownership is proven. Default false
75
+ * creates the user immediately and auto-logs them in. */
75
76
  requireEmailVerification?: boolean;
76
77
  /** When true, `POST /register` returns 409 "Email is already registered" for a
77
78
  * known email. Default false = enumeration-safe: return the same generic
@@ -1,6 +1,6 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import { type CredentialsConfig } from './config';
3
- export declare const credentialsEmailVerification: <UserType>({ credentialStore, onEmailVerified, onSendEmail, verificationTokenDurationMs, verifyEmailRoute }: CredentialsConfig<UserType>) => Elysia<"", {
3
+ export declare const credentialsEmailVerification: <UserType>({ credentialStore, getUserByEmail, onCreateCredentialUser, onEmailVerified, onRegistrationSuccess, onSendEmail, requireEmailVerification, verificationTokenDurationMs, verifyEmailRoute }: CredentialsConfig<UserType>) => Elysia<"", {
4
4
  decorator: {};
5
5
  store: {};
6
6
  derive: {};
@@ -25,19 +25,7 @@ export declare const credentialsEmailVerification: <UserType>({ credentialStore,
25
25
  query: unknown;
26
26
  headers: unknown;
27
27
  response: {
28
- 400: "Invalid or expired verification token";
29
- 200: {
30
- readonly status: "email_verified";
31
- };
32
- 422: {
33
- type: "validation";
34
- on: string;
35
- summary?: string;
36
- message?: string;
37
- found?: unknown;
38
- property?: string;
39
- expected?: string;
40
- };
28
+ [x: number]: {};
41
29
  };
42
30
  };
43
31
  };
@@ -79,6 +79,21 @@ export declare const credentialsTable: import("drizzle-orm/pg-core").PgTableWith
79
79
  identity: undefined;
80
80
  generated: undefined;
81
81
  }>;
82
+ registration_data: import("drizzle-orm/pg-core").PgBuildColumn<"auth_credentials", import("drizzle-orm/pg-core").Set$Type<import("drizzle-orm/pg-core").PgJsonbBuilder, Record<string, unknown>>, {
83
+ name: string;
84
+ tableName: "auth_credentials";
85
+ dataType: "object json";
86
+ data: Record<string, unknown>;
87
+ driverParam: unknown;
88
+ notNull: false;
89
+ hasDefault: false;
90
+ isPrimaryKey: false;
91
+ isAutoincrement: false;
92
+ hasRuntimeDefault: false;
93
+ enumValues: undefined;
94
+ identity: undefined;
95
+ generated: undefined;
96
+ }>;
82
97
  status: import("drizzle-orm/pg-core").PgBuildColumn<"auth_credentials", import("drizzle-orm/pg-core").SetHasDefault<import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>>>, {
83
98
  name: string;
84
99
  tableName: "auth_credentials";
@@ -43,19 +43,7 @@ export declare const credentialRoutes: <UserType>(config: CredentialRouteProps<U
43
43
  query: unknown;
44
44
  headers: unknown;
45
45
  response: {
46
- 400: "Invalid or expired verification token";
47
- 200: {
48
- readonly status: "email_verified";
49
- };
50
- 422: {
51
- type: "validation";
52
- on: string;
53
- summary?: string;
54
- message?: string;
55
- found?: unknown;
56
- property?: string;
57
- expected?: string;
58
- };
46
+ [x: number]: {};
59
47
  };
60
48
  };
61
49
  };
@@ -6,6 +6,12 @@ export type CredentialRecord = {
6
6
  emailVerified: boolean;
7
7
  organizationId?: OrganizationId;
8
8
  passwordHash: string;
9
+ /**
10
+ * Signup fields held until email ownership is proven. This is deliberately
11
+ * stored with the credential rather than in the consumer's user table so
12
+ * `requireEmailVerification` does not create an impersonatable account.
13
+ */
14
+ registrationData?: Record<string, unknown>;
9
15
  status: CredentialStatus;
10
16
  updatedAt: number;
11
17
  userId?: string;
package/dist/index.d.ts CHANGED
@@ -760,19 +760,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
760
760
  query: unknown;
761
761
  headers: unknown;
762
762
  response: {
763
- 400: "Invalid or expired verification token";
764
- 200: {
765
- readonly status: "email_verified";
766
- };
767
- 422: {
768
- type: "validation";
769
- on: string;
770
- summary?: string;
771
- message?: string;
772
- found?: unknown;
773
- property?: string;
774
- expected?: string;
775
- };
763
+ [x: number]: {};
776
764
  };
777
765
  };
778
766
  };
package/dist/index.js CHANGED
@@ -5124,8 +5124,12 @@ var DEFAULT_VERIFICATION_TOKEN_TTL_MS = MILLISECONDS_IN_A_DAY;
5124
5124
  // src/credentials/emailVerification.ts
5125
5125
  var credentialsEmailVerification = ({
5126
5126
  credentialStore,
5127
+ getUserByEmail,
5128
+ onCreateCredentialUser,
5127
5129
  onEmailVerified,
5130
+ onRegistrationSuccess,
5128
5131
  onSendEmail,
5132
+ requireEmailVerification = false,
5129
5133
  verificationTokenDurationMs = DEFAULT_VERIFICATION_TOKEN_TTL_MS,
5130
5134
  verifyEmailRoute = "/auth/verify-email"
5131
5135
  }) => new Elysia10().post(verifyEmailRoute, async ({ body: { token }, status }) => {
@@ -5136,7 +5140,36 @@ var credentialsEmailVerification = ({
5136
5140
  if (consumed.expiresAt < Date.now()) {
5137
5141
  return status("Bad Request", "Invalid or expired verification token");
5138
5142
  }
5143
+ const credential = await credentialStore.getCredentialByEmail(consumed.email);
5144
+ if (!credential) {
5145
+ return status("Bad Request", "Invalid or expired verification token");
5146
+ }
5147
+ let user = await getUserByEmail(consumed.email);
5148
+ if (requireEmailVerification && !user && credential.registrationData !== undefined) {
5149
+ const created = await onCreateCredentialUser({
5150
+ ...credential.registrationData,
5151
+ email: consumed.email
5152
+ });
5153
+ if (created instanceof Response || isStatusResponse(created)) {
5154
+ return created;
5155
+ }
5156
+ user = created;
5157
+ }
5139
5158
  await credentialStore.setEmailVerified(consumed.email);
5159
+ if (credential.registrationData !== undefined) {
5160
+ await credentialStore.saveCredential({
5161
+ ...credential,
5162
+ emailVerified: true,
5163
+ registrationData: undefined,
5164
+ updatedAt: Date.now()
5165
+ });
5166
+ }
5167
+ if (user && credential.registrationData !== undefined) {
5168
+ await onRegistrationSuccess?.({
5169
+ email: consumed.email,
5170
+ user
5171
+ });
5172
+ }
5140
5173
  await onEmailVerified?.({ email: consumed.email });
5141
5174
  return status("OK", { status: "email_verified" });
5142
5175
  }, { body: t7.Object({ token: t7.String() }) }).post(`${verifyEmailRoute}/request`, async ({ body: { email }, status }) => {
@@ -5510,7 +5543,7 @@ var credentialsPasswordReset = ({
5510
5543
  }) => new Elysia12().post(`${resetPasswordRoute}/request`, async ({ body: { email }, status }) => {
5511
5544
  const normalizedEmail = email.trim().toLowerCase();
5512
5545
  const credential = await credentialStore.getCredentialByEmail(normalizedEmail);
5513
- if (credential && credential.status === "active") {
5546
+ if (credential && credential.status === "active" && credential.registrationData === undefined) {
5514
5547
  const token = generateSecureToken();
5515
5548
  const expiresAt = Date.now() + resetTokenDurationMs;
5516
5549
  await credentialStore.saveResetToken({
@@ -5620,12 +5653,16 @@ var credentialsRegister = ({
5620
5653
  status: "verification_required"
5621
5654
  });
5622
5655
  }
5623
- const created = await onCreateCredentialUser({
5624
- ...extraFields,
5625
- email: normalizedEmail
5626
- });
5627
- if (created instanceof Response || isStatusResponse(created)) {
5628
- return created;
5656
+ let created;
5657
+ if (!requireEmailVerification) {
5658
+ const result = await onCreateCredentialUser({
5659
+ ...extraFields,
5660
+ email: normalizedEmail
5661
+ });
5662
+ if (result instanceof Response || isStatusResponse(result)) {
5663
+ return result;
5664
+ }
5665
+ created = result;
5629
5666
  }
5630
5667
  const now = Date.now();
5631
5668
  await credentialStore.saveCredential({
@@ -5633,6 +5670,7 @@ var credentialsRegister = ({
5633
5670
  email: normalizedEmail,
5634
5671
  emailVerified: false,
5635
5672
  passwordHash: await hashPassword(password),
5673
+ registrationData: requireEmailVerification ? extraFields : undefined,
5636
5674
  status: "active",
5637
5675
  updatedAt: now
5638
5676
  });
@@ -5649,15 +5687,18 @@ var credentialsRegister = ({
5649
5687
  token,
5650
5688
  type: "verify_email"
5651
5689
  });
5652
- await onRegistrationSuccess?.({
5653
- email: normalizedEmail,
5654
- user: created
5655
- });
5656
5690
  if (requireEmailVerification) {
5657
5691
  return status("Created", {
5658
5692
  status: "verification_required"
5659
5693
  });
5660
5694
  }
5695
+ if (created === undefined) {
5696
+ throw new Error("Credential user was not created");
5697
+ }
5698
+ await onRegistrationSuccess?.({
5699
+ email: normalizedEmail,
5700
+ user: created
5701
+ });
5661
5702
  const userSessionId = await promoteToSession({
5662
5703
  authSessionStore,
5663
5704
  cookie: user_session_id,
@@ -31531,7 +31572,7 @@ var createNeonLinkedProviderGrantStore = (db) => ({
31531
31572
  owner_ref: grant.ownerRef,
31532
31573
  provider_family: grant.providerFamily,
31533
31574
  provider_subject: grant.providerSubject,
31534
- refresh_token_ciphertext: grant.refreshTokenCiphertext ?? null,
31575
+ refresh_token_ciphertext: sql`coalesce(excluded.refresh_token_ciphertext, ${linkedProviderGrantsTable.refresh_token_ciphertext})`,
31535
31576
  status: grant.status,
31536
31577
  token_type: grant.tokenType ?? null,
31537
31578
  updated_at: new Date(grant.updatedAt)
@@ -31590,7 +31631,11 @@ var createInMemoryLinkedProviderStores = (input = {}) => {
31590
31631
  [...bindings.entries()].filter(([, binding]) => binding.grantId === id).forEach(([bindingId2]) => bindings.delete(bindingId2));
31591
31632
  },
31592
31633
  saveGrant: async (grant) => {
31593
- grants.set(grant.id, cloneGrant(grant));
31634
+ const existing = grants.get(grant.id);
31635
+ grants.set(grant.id, cloneGrant({
31636
+ ...grant,
31637
+ refreshTokenCiphertext: grant.refreshTokenCiphertext ?? existing?.refreshTokenCiphertext
31638
+ }));
31594
31639
  }
31595
31640
  };
31596
31641
  const bindingStore = {
@@ -32010,7 +32055,8 @@ var validateEmailDeliverability = async (email, options) => {
32010
32055
  };
32011
32056
  // src/credentials/inMemoryCredentialStore.ts
32012
32057
  var cloneCredential = (value) => ({
32013
- ...value
32058
+ ...value,
32059
+ registrationData: value.registrationData === undefined ? undefined : structuredClone(value.registrationData)
32014
32060
  });
32015
32061
  var cloneToken = (value) => ({ ...value });
32016
32062
  var consumeToken = (tokens, tokenHash) => {
@@ -32065,6 +32111,7 @@ var credentialsTable = pgTable("auth_credentials", {
32065
32111
  email_verified: boolean("email_verified").notNull().default(false),
32066
32112
  organization_id: varchar("organization_id", { length: ID_LENGTH2 }),
32067
32113
  password_hash: text("password_hash").notNull(),
32114
+ registration_data: jsonb("registration_data").$type(),
32068
32115
  status: varchar("status", { length: STATUS_LENGTH }).notNull().default("active"),
32069
32116
  updated_at_ms: bigint("updated_at_ms", { mode: "number" }).notNull(),
32070
32117
  user_id: varchar("user_id", { length: ID_LENGTH2 })
@@ -32086,6 +32133,7 @@ var toCredentialRecord = (row) => ({
32086
32133
  emailVerified: row.email_verified,
32087
32134
  organizationId: row.organization_id ?? undefined,
32088
32135
  passwordHash: row.password_hash,
32136
+ registrationData: row.registration_data ?? undefined,
32089
32137
  status: isCredentialStatus(row.status) ? row.status : "active",
32090
32138
  updatedAt: row.updated_at_ms,
32091
32139
  userId: row.user_id ?? undefined
@@ -32127,6 +32175,7 @@ var createPostgresCredentialStore = (db) => ({
32127
32175
  email_verified: credential.emailVerified,
32128
32176
  organization_id: credential.organizationId ?? null,
32129
32177
  password_hash: credential.passwordHash,
32178
+ registration_data: credential.registrationData ?? null,
32130
32179
  status: credential.status,
32131
32180
  updated_at_ms: credential.updatedAt,
32132
32181
  user_id: credential.userId ?? null
@@ -36771,6 +36820,10 @@ var mfaSmsColumnsMigration = {
36771
36820
  ].join(`
36772
36821
  `)
36773
36822
  };
36823
+ var credentialDeferredUserMigration = {
36824
+ id: "0002_deferred_user_creation",
36825
+ sql: 'ALTER TABLE "auth_credentials" ADD COLUMN IF NOT EXISTS "registration_data" jsonb;'
36826
+ };
36774
36827
  var mfaTotpLockoutMigration = {
36775
36828
  id: "0003_totp_lockout",
36776
36829
  sql: 'ALTER TABLE "auth_mfa_enrollments" ADD COLUMN IF NOT EXISTS "totp_failed_attempts" smallint NOT NULL DEFAULT 0;'
@@ -36826,11 +36879,17 @@ var blockMigrations = {
36826
36879
  apiKeysTable
36827
36880
  ]),
36828
36881
  audit: initMigration("audit", [auditEventsTable]),
36829
- credentials: initMigration("credentials", [
36830
- credentialsTable,
36831
- credentialResetTokensTable,
36832
- credentialVerificationTokensTable
36833
- ]),
36882
+ credentials: {
36883
+ block: "credentials",
36884
+ migrations: [
36885
+ ...initMigration("credentials", [
36886
+ credentialsTable,
36887
+ credentialResetTokensTable,
36888
+ credentialVerificationTokensTable
36889
+ ]).migrations,
36890
+ credentialDeferredUserMigration
36891
+ ]
36892
+ },
36834
36893
  fga: initMigration("fga", [warrantsTable]),
36835
36894
  linkedProviders: initMigration("linkedProviders", [
36836
36895
  linkedProviderBindingsTable,
@@ -38181,5 +38240,5 @@ export {
38181
38240
  AGENT_CLAIM_GRANT_TYPE
38182
38241
  };
38183
38242
 
38184
- //# debugId=C6EA4E2761E1FA9664756E2164756E21
38243
+ //# debugId=9CCB3DA09AA91A9764756E2164756E21
38185
38244
  //# sourceMappingURL=index.js.map