@madauth/server 0.4.0-beta.1 → 0.5.0-beta.2

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  madauthSchema
3
- } from "./chunk-GA4TF7EI.js";
3
+ } from "./chunk-NP75Y3IV.js";
4
4
 
5
5
  // src/store/sql.ts
6
6
  function tableName(model) {
@@ -15,6 +15,10 @@ var columnTypes = {
15
15
  postgres: { string: "TEXT", number: "DOUBLE PRECISION", boolean: "BOOLEAN" },
16
16
  mysql: { string: "TEXT", number: "DOUBLE", boolean: "BOOLEAN" }
17
17
  };
18
+ function columnType(dialect, def) {
19
+ const keyed = def.primaryKey || def.unique || def.index;
20
+ return dialect === "mysql" && def.type === "string" && keyed ? "VARCHAR(255)" : columnTypes[dialect][def.type];
21
+ }
18
22
  var upgrades = [
19
23
  {
20
24
  version: 2,
@@ -54,6 +58,27 @@ var upgrades = [
54
58
  `ALTER TABLE ${tableName("account")} ADD COLUMN ${columnName("setupId")} ${columnTypes[dialect].string};`,
55
59
  createTablesSql(dialect, { version: 5, models: { recoveryCode: madauthSchema.models.recoveryCode } })
56
60
  ].join("\n")
61
+ },
62
+ {
63
+ // SMS codes as a second factor: the phone number of an sms account, and when the last SMS went to a user.
64
+ version: 6,
65
+ sql: (dialect) => [
66
+ `ALTER TABLE ${tableName("account")} ADD COLUMN ${columnName("phone")} ${columnTypes[dialect].string};`,
67
+ `ALTER TABLE ${tableName("user")} ADD COLUMN ${columnName("lastSmsAt")} ${columnTypes[dialect].number};`
68
+ ].join("\n")
69
+ },
70
+ {
71
+ // One primary sign-in method per user, which accounts are second factors, and when a user's sign-up was
72
+ // complete. Older records stay empty: madAuth tells them apart by the account's key, and counts their
73
+ // sign-up as complete.
74
+ version: 7,
75
+ sql: (dialect) => [
76
+ `ALTER TABLE ${tableName("user")} ADD COLUMN ${columnName("signedUpAt")} ${columnTypes[dialect].number};`,
77
+ // SQLite can't add a UNIQUE column, so the constraint is an index.
78
+ `ALTER TABLE ${tableName("account")} ADD COLUMN ${columnName("primaryUserId")} ${columnType(dialect, madauthSchema.models.account.fields.primaryUserId)};`,
79
+ `CREATE UNIQUE INDEX ${tableName("account")}_${columnName("primaryUserId")} ON ${tableName("account")} (${columnName("primaryUserId")});`,
80
+ `ALTER TABLE ${tableName("account")} ADD COLUMN ${columnName("secondFactor")} ${columnType(dialect, madauthSchema.models.account.fields.secondFactor)};`
81
+ ].join("\n")
57
82
  }
58
83
  ];
59
84
  function upgradeTablesSql(dialect, fromVersion) {
@@ -64,8 +89,7 @@ function createTablesSql(dialect, schema = madauthSchema) {
64
89
  for (const [model, { fields }] of Object.entries(schema.models)) {
65
90
  const table = tableName(model);
66
91
  const columns = Object.entries(fields).map(([field, def]) => {
67
- const keyed = def.primaryKey || def.unique || def.index;
68
- const type = dialect === "mysql" && def.type === "string" && keyed ? "VARCHAR(255)" : columnTypes[dialect][def.type];
92
+ const type = columnType(dialect, def);
69
93
  const constraints = def.primaryKey ? " PRIMARY KEY" : `${def.nullable ? "" : " NOT NULL"}${def.unique ? " UNIQUE" : ""}`;
70
94
  return ` ${columnName(field)} ${type}${constraints}`;
71
95
  });
@@ -1,27 +1,29 @@
1
1
  // src/webhooks.ts
2
2
  import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "crypto";
3
3
  var WEBHOOK_TYPES = [
4
- "email.verify",
5
- "email.reset",
6
- "email.already_registered",
7
- "email.no_password",
8
- "signup.before",
9
- "user.created",
10
- "email.verified",
11
- "email.password_reset",
4
+ "email.send_verification",
5
+ "email.send_password_reset",
6
+ "email.send_signin_link",
7
+ "email.send_already_registered",
8
+ "email.send_password_reset_no_password",
9
+ "sms.send_code",
10
+ "user.signing_up",
11
+ "user.signed_up",
12
+ "user.email_verified",
13
+ "user.password_reset",
12
14
  "user.signed_in",
13
15
  "user.deleted",
14
16
  "user.claims_changed",
15
- "totp.enabled",
16
- "totp.disabled",
17
- "totp.recovery_code_used"
17
+ "user.second_factor_enabled",
18
+ "user.second_factor_disabled",
19
+ "user.recovery_code_used"
18
20
  ];
19
21
  var WEBHOOK_TIMEOUT_MS = 1e4;
20
22
  var MAX_LOCALE_LENGTH = 35;
21
23
  function localeOf(value) {
22
24
  return typeof value === "string" && value.length <= MAX_LOCALE_LENGTH ? value : void 0;
23
25
  }
24
- var REQUIRED_EMAIL_TYPES = ["email.verify", "email.reset"];
26
+ var REQUIRED_EMAIL_TYPES = ["email.send_verification", "email.send_password_reset"];
25
27
  var SECRET_PREFIX = "whsec_";
26
28
  var DEV_WEBHOOK_RECEIVER_URL = "https://github.com/inouiw/madAuth-webhooks/tree/main/dev-receiver";
27
29
  var WEBHOOK_TOLERANCE_SECONDS = 5 * 60;
@@ -1,6 +1,6 @@
1
1
  import { JWK, JWTVerifyGetKey } from 'jose';
2
- import { S as StoreAdapter } from './schema-DTeiSuQB.js';
3
- import { W as WebhookSettings } from './webhook-B6P50ocX.js';
2
+ import { S as StoreAdapter } from './schema-BTgDj1Xn.js';
3
+ import { W as WebhookSettings } from './webhook-DlmfG_kS.js';
4
4
 
5
5
  interface MadauthConfig {
6
6
  /** Public base URL of the server (MADAUTH_ISSUER). */
@@ -24,13 +24,20 @@ interface MadauthConfig {
24
24
  /** Enables the server-side code flow (GOOGLE_CLIENT_SECRET). */
25
25
  clientSecret?: string;
26
26
  };
27
+ /** GitHub sign-in; off when GITHUB_CLIENT_ID is not set. GitHub only has the redirect flow, so the secret is needed too. */
28
+ github?: {
29
+ /** The client ID of the OAuth App (GITHUB_CLIENT_ID). */
30
+ clientId: string;
31
+ /** Its client secret (GITHUB_CLIENT_SECRET). */
32
+ clientSecret: string;
33
+ };
27
34
  /** Where the users, their accounts, claims and the settings are kept (DATABASE_URL or the `store` option). */
28
35
  store: StoreAdapter;
29
- /** E-mail & password sign-in; off unless the webhook sends the e-mails (`email.verify` and `email.reset`). */
30
- password?: {
31
- /** PASSWORD_MIN_LENGTH. */
32
- minLength: number;
33
- };
36
+ /**
37
+ * E-mail & password sign-in; off unless the webhook sends the e-mails (`email.send_verification` and `email.send_password_reset`).
38
+ * Its rules for new passwords are a setting, see `Settings.passwordRules`.
39
+ */
40
+ password?: Record<string, never>;
34
41
  /** Where madAuth sends e-mails, sign-up checks and events (WEBHOOK_URL). */
35
42
  webhook?: WebhookSettings;
36
43
  /** Test hook: resolves Google's signing keys. Defaults to Google's published JWKS. */
@@ -45,8 +52,9 @@ declare const envVars: {
45
52
  readonly ALLOWED_ORIGINS: true;
46
53
  readonly GOOGLE_CLIENT_ID: false;
47
54
  readonly GOOGLE_CLIENT_SECRET: false;
55
+ readonly GITHUB_CLIENT_ID: false;
56
+ readonly GITHUB_CLIENT_SECRET: false;
48
57
  readonly DATABASE_URL: true;
49
- readonly PASSWORD_MIN_LENGTH: false;
50
58
  readonly TOTP_ISSUER: false;
51
59
  readonly WEBHOOK_URL: false;
52
60
  readonly WEBHOOK_SECRET: false;
package/dist/dynamodb.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  madauthSchema
3
- } from "./chunk-GA4TF7EI.js";
3
+ } from "./chunk-NP75Y3IV.js";
4
4
 
5
5
  // src/store/dynamodb.ts
6
6
  var SORT_KEY = "#";
@@ -1,7 +1,7 @@
1
- import { E as EntryOptions } from '../config-DkE_2D3-.js';
1
+ import { E as EntryOptions } from '../config-CcGlHVAN.js';
2
2
  import 'jose';
3
- import '../schema-DTeiSuQB.js';
4
- import '../webhook-B6P50ocX.js';
3
+ import '../schema-BTgDj1Xn.js';
4
+ import '../webhook-DlmfG_kS.js';
5
5
 
6
6
  /**
7
7
  * Registers madAuth as an Azure Functions v4 HTTP function that handles every route. Pass `store` to use your
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-3PACOYTG.js";
3
+ } from "../chunk-6SF2WNOC.js";
4
4
  import "../chunk-A7UYZRBL.js";
5
5
  import {
6
6
  loadConfig,
7
7
  resolveEnv
8
- } from "../chunk-ISJGOCF3.js";
8
+ } from "../chunk-NYIEFI4B.js";
9
9
  import "../chunk-JDQLSL36.js";
10
- import "../chunk-Y7ZSUROX.js";
10
+ import "../chunk-SISM7YUZ.js";
11
11
 
12
12
  // src/entry/azure.ts
13
13
  import { app } from "@azure/functions";
@@ -1,8 +1,8 @@
1
1
  import { LambdaEvent, LambdaContext, handle } from 'hono/aws-lambda';
2
- import { E as EntryOptions } from '../config-DkE_2D3-.js';
2
+ import { E as EntryOptions } from '../config-CcGlHVAN.js';
3
3
  import 'jose';
4
- import '../schema-DTeiSuQB.js';
5
- import '../webhook-B6P50ocX.js';
4
+ import '../schema-BTgDj1Xn.js';
5
+ import '../webhook-DlmfG_kS.js';
6
6
 
7
7
  type LambdaHandler = (event: LambdaEvent, context: LambdaContext) => ReturnType<ReturnType<typeof handle>>;
8
8
  /**
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-3PACOYTG.js";
3
+ } from "../chunk-6SF2WNOC.js";
4
4
  import "../chunk-A7UYZRBL.js";
5
5
  import {
6
6
  loadConfig,
7
7
  resolveEnv
8
- } from "../chunk-ISJGOCF3.js";
8
+ } from "../chunk-NYIEFI4B.js";
9
9
  import "../chunk-JDQLSL36.js";
10
- import "../chunk-Y7ZSUROX.js";
10
+ import "../chunk-SISM7YUZ.js";
11
11
 
12
12
  // src/entry/lambda.ts
13
13
  import { handle } from "hono/aws-lambda";
@@ -1,7 +1,7 @@
1
- import { E as EntryOptions } from '../config-DkE_2D3-.js';
1
+ import { E as EntryOptions } from '../config-CcGlHVAN.js';
2
2
  import 'jose';
3
- import '../schema-DTeiSuQB.js';
4
- import '../webhook-B6P50ocX.js';
3
+ import '../schema-BTgDj1Xn.js';
4
+ import '../webhook-DlmfG_kS.js';
5
5
 
6
6
  type StartOptions = EntryOptions;
7
7
  /**
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-3PACOYTG.js";
3
+ } from "../chunk-6SF2WNOC.js";
4
4
  import "../chunk-A7UYZRBL.js";
5
5
  import {
6
6
  loadConfig,
7
7
  resolveEnv
8
- } from "../chunk-ISJGOCF3.js";
8
+ } from "../chunk-NYIEFI4B.js";
9
9
  import "../chunk-JDQLSL36.js";
10
- import "../chunk-Y7ZSUROX.js";
10
+ import "../chunk-SISM7YUZ.js";
11
11
 
12
12
  // src/entry/node.ts
13
13
  import { serve } from "@hono/node-server";
package/dist/index.d.ts CHANGED
@@ -1,37 +1,44 @@
1
1
  import { Hono } from 'hono';
2
- import { M as MadauthConfig } from './config-DkE_2D3-.js';
3
- export { C as ConfigError, a as ConfigOverrides, E as EntryOptions, e as envVars, l as loadConfig } from './config-DkE_2D3-.js';
2
+ import { M as MadauthConfig } from './config-CcGlHVAN.js';
3
+ export { C as ConfigError, a as ConfigOverrides, E as EntryOptions, e as envVars, l as loadConfig } from './config-CcGlHVAN.js';
4
4
  import { JWK } from 'jose';
5
- import { a as Schema } from './schema-DTeiSuQB.js';
6
- export { F as FieldDef, b as FieldType, R as Row, S as StoreAdapter, V as Value, W as Where, m as madauthSchema } from './schema-DTeiSuQB.js';
7
- export { M as MadauthUser } from './user-BZs84sui.js';
8
- import './webhook-B6P50ocX.js';
5
+ import { a as Schema } from './schema-BTgDj1Xn.js';
6
+ export { F as FieldDef, b as FieldType, R as Row, S as StoreAdapter, V as Value, W as Where, m as madauthSchema } from './schema-BTgDj1Xn.js';
7
+ export { M as MadauthUser } from './user-yoGjrw4k.js';
8
+ import './webhook-DlmfG_kS.js';
9
9
 
10
10
  /**
11
- * The primary sign-in methods madAuth has: Google, e-mail & password, and the authenticator app on its own
12
- * (the e-mail address and a code from the app).
11
+ * The primary sign-in methods madAuth has: Google, GitHub, e-mail & password, the authenticator app on its
12
+ * own (`totp`: the e-mail address and a code from the app), and the passwordless e-mail link (`email-link`:
13
+ * the address, then the link or the code from an e-mail).
13
14
  */
14
- declare const SIGN_IN_METHODS: readonly ["google", "password", "totp"];
15
+ declare const SIGN_IN_METHODS: readonly ["google", "github", "password", "totp", "email-link"];
15
16
  type SignInMethod = (typeof SIGN_IN_METHODS)[number];
16
- /** The methods that can ask for a code from the authenticator app after they succeeded. */
17
- declare const METHODS_WITH_SECOND_FACTOR: readonly ["google", "password"];
17
+ /**
18
+ * Every key of the methods setting: the primary methods, and `sms`, which signs nobody in by itself: SMS
19
+ * codes as a second factor of the methods whose policy asks for one, next to the authenticator app.
20
+ */
21
+ declare const METHODS: readonly ["google", "github", "password", "totp", "email-link", "sms"];
22
+ type MethodId = (typeof METHODS)[number];
23
+ /** The methods that can ask for a second factor (a code from the authenticator app) after they succeeded. */
24
+ declare const METHODS_WITH_SECOND_FACTOR: readonly ["google", "github", "password", "email-link"];
18
25
  /**
19
26
  * Whether a method asks for the authenticator app as a second factor: never, when the user has set one up
20
27
  * (`optional`), or always, so that a user without one sets it up at the next sign-in (`required`).
21
28
  */
22
29
  declare const SECOND_FACTOR_POLICIES: readonly ["none", "optional", "required"];
23
30
  type SecondFactorPolicy = (typeof SECOND_FACTOR_POLICIES)[number];
24
- /** The settings of a method that is on. `secondFactor` is for Google and password; it defaults to `none`. */
31
+ /** The settings of a method that is on. `secondFactor` is for the METHODS_WITH_SECOND_FACTOR; it defaults to `none`. */
25
32
  interface MethodSetting {
26
33
  secondFactor?: SecondFactorPolicy;
27
34
  }
28
35
  /** The sign-in methods that are on, each with its settings. A method that is not listed is off. */
29
- type MethodSettings = Partial<Record<SignInMethod, MethodSetting>>;
36
+ type MethodSettings = Partial<Record<MethodId, MethodSetting>>;
30
37
 
31
38
  /** Creates a new private ES256 JWK for MADAUTH_SIGNING_KEY. */
32
39
  declare function generateSigningKey(): Promise<JWK>;
33
40
 
34
- /** Hash parameter that carries a sign-in error back to the app after the code flow. */
41
+ /** Hash parameter that carries a sign-in error back to the app after the redirect flow. */
35
42
  declare const REDIRECT_ERROR_PARAM = "madauth_error";
36
43
 
37
44
  /**
@@ -70,4 +77,4 @@ declare function parseRoles(input: unknown): string[] | null;
70
77
  */
71
78
  declare function parseClaims(input: unknown): Claims | null;
72
79
 
73
- export { ADMIN_ROLE, type Claims, METHODS_WITH_SECOND_FACTOR, MadauthConfig, type MethodSetting, type MethodSettings, REDIRECT_ERROR_PARAM, SECOND_FACTOR_POLICIES, SIGN_IN_METHODS, Schema, type SecondFactorPolicy, type SignInMethod, type SqlDialect, columnName, createApp, createTablesSql, generateSigningKey, parseClaims, parseRoles, tableName, upgradeTablesSql };
80
+ export { ADMIN_ROLE, type Claims, METHODS, METHODS_WITH_SECOND_FACTOR, MadauthConfig, type MethodId, type MethodSetting, type MethodSettings, REDIRECT_ERROR_PARAM, SECOND_FACTOR_POLICIES, SIGN_IN_METHODS, Schema, type SecondFactorPolicy, type SignInMethod, type SqlDialect, columnName, createApp, createTablesSql, generateSigningKey, parseClaims, parseRoles, tableName, upgradeTablesSql };
package/dist/index.js CHANGED
@@ -3,18 +3,19 @@ import {
3
3
  createTablesSql,
4
4
  tableName,
5
5
  upgradeTablesSql
6
- } from "./chunk-OMIX3ERC.js";
6
+ } from "./chunk-O2URPWA6.js";
7
7
  import {
8
8
  madauthSchema
9
- } from "./chunk-GA4TF7EI.js";
9
+ } from "./chunk-NP75Y3IV.js";
10
10
  import {
11
11
  REDIRECT_ERROR_PARAM,
12
12
  createApp
13
- } from "./chunk-3PACOYTG.js";
13
+ } from "./chunk-6SF2WNOC.js";
14
14
  import "./chunk-A7UYZRBL.js";
15
15
  import {
16
16
  ADMIN_ROLE,
17
17
  ConfigError,
18
+ METHODS,
18
19
  METHODS_WITH_SECOND_FACTOR,
19
20
  SECOND_FACTOR_POLICIES,
20
21
  SIGN_IN_METHODS,
@@ -22,14 +23,15 @@ import {
22
23
  loadConfig,
23
24
  parseClaims,
24
25
  parseRoles
25
- } from "./chunk-ISJGOCF3.js";
26
+ } from "./chunk-NYIEFI4B.js";
26
27
  import {
27
28
  generateSigningKey
28
29
  } from "./chunk-JDQLSL36.js";
29
- import "./chunk-Y7ZSUROX.js";
30
+ import "./chunk-SISM7YUZ.js";
30
31
  export {
31
32
  ADMIN_ROLE,
32
33
  ConfigError,
34
+ METHODS,
33
35
  METHODS_WITH_SECOND_FACTOR,
34
36
  REDIRECT_ERROR_PARAM,
35
37
  SECOND_FACTOR_POLICIES,
@@ -43,7 +43,7 @@ interface Schema {
43
43
  }
44
44
  /** The records madAuth stores. Timestamps are milliseconds since 1970. */
45
45
  declare const madauthSchema: {
46
- readonly version: 5;
46
+ readonly version: 7;
47
47
  readonly models: {
48
48
  /** Someone who signed in, whichever way: the e-mail address is the identity shared by the sign-in methods. */
49
49
  readonly user: {
@@ -69,9 +69,15 @@ declare const madauthSchema: {
69
69
  readonly sessionVersion: {
70
70
  readonly type: "number";
71
71
  };
72
+ /** When the last e-mail was sent to the user: at most one per minute. */
72
73
  readonly lastMailAt: {
73
74
  readonly type: "number";
74
75
  };
76
+ /** When the last SMS was sent to the user: at most one per minute, counted apart from the e-mails (since version 6). */
77
+ readonly lastSmsAt: {
78
+ readonly type: "number";
79
+ readonly nullable: true;
80
+ };
75
81
  /** Wrong e-mail codes in a row, counted across e-mails (since version 2). */
76
82
  readonly wrongCodes: {
77
83
  readonly type: "number";
@@ -81,14 +87,24 @@ declare const madauthSchema: {
81
87
  readonly type: "string";
82
88
  readonly nullable: true;
83
89
  };
90
+ /**
91
+ * When the user's first session started: the sign-up was complete, with every second factor the
92
+ * policy asked for. 0 until then. Empty in records from before version 7, whose sign-up counts as
93
+ * complete (since version 7).
94
+ */
95
+ readonly signedUpAt: {
96
+ readonly type: "number";
97
+ readonly nullable: true;
98
+ };
84
99
  readonly createdAt: {
85
100
  readonly type: "number";
86
101
  };
87
102
  };
88
103
  };
89
104
  /**
90
- * How a user signs in. `key` is `password:<userId>`, `google:<sub>` or `totp:<userId>` (the authenticator
91
- * app, whose `secret` is encrypted).
105
+ * How a user signs in. `key` is `password:<userId>`, `google:<sub>`, `totp:<userId>` (the authenticator
106
+ * app, whose `secret` is encrypted), `email-link:<userId>` or `sms:<userId>` (the phone that gets the
107
+ * codes of the second factor).
92
108
  */
93
109
  readonly account: {
94
110
  readonly fields: {
@@ -131,6 +147,29 @@ declare const madauthSchema: {
131
147
  readonly type: "string";
132
148
  readonly nullable: true;
133
149
  };
150
+ /** The phone number of an `sms:` account, in E.164 form (`+41791234567`). Empty for other accounts (since version 6). */
151
+ readonly phone: {
152
+ readonly type: "string";
153
+ readonly nullable: true;
154
+ };
155
+ /**
156
+ * The user's id when the account is how they sign in (their one primary method), empty for a second
157
+ * factor. Unique, so a user has one primary method at most (since version 7; empty in older records).
158
+ */
159
+ readonly primaryUserId: {
160
+ readonly type: "string";
161
+ readonly nullable: true;
162
+ readonly unique: true;
163
+ };
164
+ /**
165
+ * Whether the account is a second factor (an authenticator app that is a step of the user's sign-in
166
+ * method, or a phone for SMS codes) rather than a sign-in method. Empty in records from before version 7,
167
+ * which madAuth tells apart by their key (since version 7).
168
+ */
169
+ readonly secondFactor: {
170
+ readonly type: "boolean";
171
+ readonly nullable: true;
172
+ };
134
173
  readonly createdAt: {
135
174
  readonly type: "number";
136
175
  };
@@ -158,7 +197,7 @@ declare const madauthSchema: {
158
197
  };
159
198
  };
160
199
  };
161
- /** A pending e-mail verification or password reset. `id` is the SHA-256 of the link token. */
200
+ /** A pending e-mail verification, password reset, sign-in link or SMS code. `id` is the SHA-256 of the link token. */
162
201
  readonly verification: {
163
202
  readonly fields: {
164
203
  readonly id: {
package/dist/sqlite.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  createTablesSql,
4
4
  tableName,
5
5
  upgradeTablesSql
6
- } from "./chunk-OMIX3ERC.js";
6
+ } from "./chunk-O2URPWA6.js";
7
7
  import {
8
8
  madauthSchema
9
- } from "./chunk-GA4TF7EI.js";
9
+ } from "./chunk-NP75Y3IV.js";
10
10
 
11
11
  // src/store/sqlite.ts
12
12
  function createSqliteAdapter(path) {