@owlmeans/auth-email 0.0.0-stage → 0.1.19

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,66 @@
1
+ import { ResilientError } from '@owlmeans/error';
2
+ /**
3
+ * The e-mail sign-in error family, shared by the server that throws and the client that reads it
4
+ * back (each class is registered, so it survives the wire). A failed PIN or password check is
5
+ * `AuthenFailed` from `@owlmeans/auth`, never one of these: no answer here may tell an unknown
6
+ * address from a wrong credential.
7
+ */
8
+ export class EmailAuthError extends ResilientError {
9
+ static typeName = 'EmailAuthError';
10
+ constructor(message = 'error') {
11
+ super(EmailAuthError.typeName, `auth-email:${message}`);
12
+ }
13
+ }
14
+ /** A password sign-in or a password route on a deployment that does not offer passwords. */
15
+ export class EmailPasswordDisabled extends EmailAuthError {
16
+ static typeName = `${EmailAuthError.typeName}PasswordDisabled`;
17
+ static httpStatus = 404;
18
+ constructor(message = 'password') {
19
+ super(`disabled:${message}`);
20
+ this.type = EmailPasswordDisabled.typeName;
21
+ }
22
+ }
23
+ /** A new password the policy refuses (length bounds); the message names the rule. */
24
+ export class EmailPasswordWeak extends EmailAuthError {
25
+ static typeName = `${EmailAuthError.typeName}PasswordWeak`;
26
+ static httpStatus = 422;
27
+ constructor(message = 'length') {
28
+ super(`weak:${message}`);
29
+ this.type = EmailPasswordWeak.typeName;
30
+ }
31
+ }
32
+ /** The one-use offer ticket is spent, expired or another account's — confirm with a PIN instead. */
33
+ export class EmailOfferExpired extends EmailAuthError {
34
+ static typeName = `${EmailAuthError.typeName}OfferExpired`;
35
+ static httpStatus = 409;
36
+ constructor(message = 'offer') {
37
+ super(`expired:${message}`);
38
+ this.type = EmailOfferExpired.typeName;
39
+ }
40
+ }
41
+ /** A developer (tech) route on a deployment where they are off: answers as if it did not exist. */
42
+ export class EmailTechDisabled extends EmailAuthError {
43
+ static typeName = `${EmailAuthError.typeName}TechDisabled`;
44
+ static httpStatus = 404;
45
+ constructor(message = 'tech') {
46
+ super(`disabled:${message}`);
47
+ this.type = EmailTechDisabled.typeName;
48
+ }
49
+ }
50
+ /** A store, mailer or captcha authority could not be consulted, so the request fails closed. */
51
+ export class EmailAuthUnavailable extends EmailAuthError {
52
+ static typeName = `${EmailAuthError.typeName}Unavailable`;
53
+ static httpStatus = 503;
54
+ static allowServerErrorStatus = true;
55
+ constructor(message = 'unavailable') {
56
+ super(`unavailable:${message}`);
57
+ this.type = EmailAuthUnavailable.typeName;
58
+ }
59
+ }
60
+ ResilientError.registerErrorClass(EmailAuthError);
61
+ ResilientError.registerErrorClass(EmailPasswordDisabled);
62
+ ResilientError.registerErrorClass(EmailPasswordWeak);
63
+ ResilientError.registerErrorClass(EmailOfferExpired);
64
+ ResilientError.registerErrorClass(EmailTechDisabled);
65
+ ResilientError.registerErrorClass(EmailAuthUnavailable);
66
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,OAAO,cAAe,SAAQ,cAAc;IACzC,MAAM,CAAU,QAAQ,GAAW,gBAAgB,CAAA;IAE1D,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,cAAc,CAAC,QAAQ,EAAE,cAAc,OAAO,EAAE,CAAC,CAAA;IACzD,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,OAAO,qBAAsB,SAAQ,cAAc;IAChD,MAAM,CAAU,QAAQ,GAAW,GAAG,cAAc,CAAC,QAAQ,kBAAkB,CAAA;IAC/E,MAAM,CAAC,UAAU,GAAG,GAAG,CAAA;IAE9B,YAAY,OAAO,GAAW,UAAU;QACtC,KAAK,CAAC,YAAY,OAAO,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC,QAAQ,CAAA;IAC5C,CAAC;CACF;AAED,qFAAqF;AACrF,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IAC5C,MAAM,CAAU,QAAQ,GAAW,GAAG,cAAc,CAAC,QAAQ,cAAc,CAAA;IAC3E,MAAM,CAAC,UAAU,GAAG,GAAG,CAAA;IAE9B,YAAY,OAAO,GAAW,QAAQ;QACpC,KAAK,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,QAAQ,CAAA;IACxC,CAAC;CACF;AAED,oGAAoG;AACpG,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IAC5C,MAAM,CAAU,QAAQ,GAAW,GAAG,cAAc,CAAC,QAAQ,cAAc,CAAA;IAC3E,MAAM,CAAC,UAAU,GAAG,GAAG,CAAA;IAE9B,YAAY,OAAO,GAAW,OAAO;QACnC,KAAK,CAAC,WAAW,OAAO,EAAE,CAAC,CAAA;QAC3B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,QAAQ,CAAA;IACxC,CAAC;CACF;AAED,mGAAmG;AACnG,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IAC5C,MAAM,CAAU,QAAQ,GAAW,GAAG,cAAc,CAAC,QAAQ,cAAc,CAAA;IAC3E,MAAM,CAAC,UAAU,GAAG,GAAG,CAAA;IAE9B,YAAY,OAAO,GAAW,MAAM;QAClC,KAAK,CAAC,YAAY,OAAO,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,QAAQ,CAAA;IACxC,CAAC;CACF;AAED,gGAAgG;AAChG,MAAM,OAAO,oBAAqB,SAAQ,cAAc;IAC/C,MAAM,CAAU,QAAQ,GAAW,GAAG,cAAc,CAAC,QAAQ,aAAa,CAAA;IAC1E,MAAM,CAAC,UAAU,GAAG,GAAG,CAAA;IACvB,MAAM,CAAC,sBAAsB,GAAG,IAAI,CAAA;IAE3C,YAAY,OAAO,GAAW,aAAa;QACzC,KAAK,CAAC,eAAe,OAAO,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC,QAAQ,CAAA;IAC3C,CAAC;CACF;AAED,cAAc,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA;AACjD,cAAc,CAAC,kBAAkB,CAAC,qBAAqB,CAAC,CAAA;AACxD,cAAc,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;AACpD,cAAc,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;AACpD,cAAc,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;AACpD,cAAc,CAAC,kBAAkB,CAAC,oBAAoB,CAAC,CAAA"}
@@ -0,0 +1,6 @@
1
+ export type * from './types.js';
2
+ export * from './consts.js';
3
+ export * from './schemas.js';
4
+ export * from './errors.js';
5
+ export * from './entrypoints.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA"}
package/build/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './consts.js';
2
+ export * from './schemas.js';
3
+ export * from './errors.js';
4
+ export * from './entrypoints.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA"}
@@ -0,0 +1,12 @@
1
+ import type { JSONSchemaType } from 'ajv';
2
+ import type { EmailEmptyRequest, EmailPasswordPinRequest, EmailPasswordRemoveRequest, EmailPasswordSetRequest, EmailPinRequest, EmailSignInRequest, EmailTechPinRequest } from './types.js';
3
+ export declare const EmailPinRequestSchema: JSONSchemaType<EmailPinRequest>;
4
+ /** One schema for both modes; the server enforces exactly one of PIN (`challenge` + `pin`) or `password`. */
5
+ export declare const EmailSignInRequestSchema: JSONSchemaType<EmailSignInRequest>;
6
+ export declare const EmailPasswordPinRequestSchema: JSONSchemaType<EmailPasswordPinRequest>;
7
+ export declare const EmailPasswordSetRequestSchema: JSONSchemaType<EmailPasswordSetRequest>;
8
+ export declare const EmailPasswordRemoveRequestSchema: JSONSchemaType<EmailPasswordRemoveRequest>;
9
+ export declare const EmailTechPinRequestSchema: JSONSchemaType<EmailTechPinRequest>;
10
+ /** The body of `password/decline` and `tech/waiver`: an empty object. */
11
+ export declare const EmailEmptyRequestSchema: JSONSchemaType<EmailEmptyRequest>;
12
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AAKzC,OAAO,KAAK,EACV,iBAAiB,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,uBAAuB,EAC/F,eAAe,EAAE,kBAAkB,EAAE,mBAAmB,EACzD,MAAM,YAAY,CAAA;AAYnB,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,eAAe,CASjE,CAAA;AAED,6GAA6G;AAC7G,eAAO,MAAM,wBAAwB,EAAE,cAAc,CAAC,kBAAkB,CAavE,CAAA;AAED,eAAO,MAAM,6BAA6B,EAAE,cAAc,CAAC,uBAAuB,CAQjF,CAAA;AAED,eAAO,MAAM,6BAA6B,EAAE,cAAc,CAAC,uBAAuB,CAWjF,CAAA;AAED,eAAO,MAAM,gCAAgC,EAAE,cAAc,CAAC,0BAA0B,CASvF,CAAA;AAED,eAAO,MAAM,yBAAyB,EAAE,cAAc,CAAC,mBAAmB,CAKzE,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,uBAAuB,EAKpB,cAAc,CAAC,iBAAiB,CAAC,CAAA"}
@@ -0,0 +1,79 @@
1
+ import { EMAIL_ADDRESS_MAX_LENGTH, EMAIL_DEVICE_ID_PATTERN, EMAIL_LOCALE_MAX_LENGTH, EMAIL_PASSWORD_WIRE_MAX_LENGTH, EMAIL_PIN_PATTERN, EMAIL_TOKEN_MAX_LENGTH, EmailPasswordPurpose, } from './consts.js';
2
+ // Every request schema refuses unknown fields; no body carries an organization or a source.
3
+ // The address is lowercased and trimmed by the server; the password policy is checked there too.
4
+ const email = { type: 'string', format: 'email', minLength: 3, maxLength: EMAIL_ADDRESS_MAX_LENGTH };
5
+ const device = { type: 'string', pattern: EMAIL_DEVICE_ID_PATTERN };
6
+ const pin = { type: 'string', pattern: EMAIL_PIN_PATTERN };
7
+ const token = { type: 'string', minLength: 1, maxLength: EMAIL_TOKEN_MAX_LENGTH };
8
+ const password = { type: 'string', minLength: 1, maxLength: EMAIL_PASSWORD_WIRE_MAX_LENGTH };
9
+ const locale = { type: 'string', minLength: 1, maxLength: EMAIL_LOCALE_MAX_LENGTH };
10
+ export const EmailPinRequestSchema = {
11
+ type: 'object',
12
+ properties: {
13
+ email,
14
+ locale: { ...locale, nullable: true },
15
+ captcha: { ...token, nullable: true },
16
+ },
17
+ required: ['email'],
18
+ additionalProperties: false,
19
+ };
20
+ /** One schema for both modes; the server enforces exactly one of PIN (`challenge` + `pin`) or `password`. */
21
+ export const EmailSignInRequestSchema = {
22
+ type: 'object',
23
+ properties: {
24
+ email,
25
+ device,
26
+ challenge: { ...token, nullable: true },
27
+ pin: { ...pin, nullable: true },
28
+ password: { ...password, nullable: true },
29
+ captcha: { ...token, nullable: true },
30
+ locale: { ...locale, nullable: true },
31
+ },
32
+ required: ['email', 'device'],
33
+ additionalProperties: false,
34
+ };
35
+ export const EmailPasswordPinRequestSchema = {
36
+ type: 'object',
37
+ properties: {
38
+ purpose: { type: 'string', enum: Object.values(EmailPasswordPurpose) },
39
+ locale: { ...locale, nullable: true },
40
+ },
41
+ required: ['purpose'],
42
+ additionalProperties: false,
43
+ };
44
+ export const EmailPasswordSetRequestSchema = {
45
+ type: 'object',
46
+ properties: {
47
+ password,
48
+ offer: { ...token, nullable: true },
49
+ challenge: { ...token, nullable: true },
50
+ pin: { ...pin, nullable: true },
51
+ device: { ...device, nullable: true },
52
+ },
53
+ required: ['password'],
54
+ additionalProperties: false,
55
+ };
56
+ export const EmailPasswordRemoveRequestSchema = {
57
+ type: 'object',
58
+ properties: {
59
+ challenge: token,
60
+ pin,
61
+ device: { ...device, nullable: true },
62
+ },
63
+ required: ['challenge', 'pin'],
64
+ additionalProperties: false,
65
+ };
66
+ export const EmailTechPinRequestSchema = {
67
+ type: 'object',
68
+ properties: { challenge: token, pin },
69
+ required: ['challenge', 'pin'],
70
+ additionalProperties: false,
71
+ };
72
+ /** The body of `password/decline` and `tech/waiver`: an empty object. */
73
+ export const EmailEmptyRequestSchema = {
74
+ type: 'object',
75
+ properties: {},
76
+ required: [],
77
+ additionalProperties: false,
78
+ };
79
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ import type { EntrypointProtocol, OpenRequest, OpenValue } from '@owlmeans/entrypoint';
2
+ import type { RouteParent } from '@owlmeans/route';
3
+ import type { EMAIL_AUTH_LOCALES, EMAIL_CAPTCHA_PROVIDER, EmailPasswordPurpose, EmailSignInReason } from './consts.js';
4
+ /** One of the languages the sign-in mails are written in. */
5
+ export type EmailAuthLocale = typeof EMAIL_AUTH_LOCALES[number];
6
+ /** The captcha a client must solve before send-PIN and password sign-in. */
7
+ export interface EmailAuthCaptchaView {
8
+ provider: typeof EMAIL_CAPTCHA_PROVIDER;
9
+ siteKey: string;
10
+ }
11
+ /** `GET options` — what this deployment offers; asks for no address. */
12
+ export interface EmailAuthOptionsView {
13
+ /** Password sign-in and the password routes are served. */
14
+ password: boolean;
15
+ /** `null` when captcha is off (or a valid developer waiver came with the request). */
16
+ captcha: EmailAuthCaptchaView | null;
17
+ pinLength: number;
18
+ }
19
+ /** A PIN was mailed: the signed challenge to send back with it. */
20
+ export interface EmailChallengeView {
21
+ challenge: string;
22
+ }
23
+ /**
24
+ * `POST sign-in`. A signed-in answer carries `token` (and, after a PIN sign-in of a person without
25
+ * a password who has not declined one, the one-use `offer` ticket); a password sign-in on an
26
+ * unknown device carries `challenge` + `reason: device` instead — a PIN was mailed and the password
27
+ * was not checked.
28
+ */
29
+ export interface EmailSignInView {
30
+ token?: string;
31
+ offer?: string;
32
+ challenge?: string;
33
+ reason?: EmailSignInReason;
34
+ }
35
+ /** The signed-in account's password state. */
36
+ export interface EmailPasswordStatusView {
37
+ /** Passwords are enabled on this deployment. */
38
+ enabled: boolean;
39
+ set: boolean;
40
+ /** The person declined the post-sign-in offer (or removed their password). */
41
+ declined: boolean;
42
+ /** ISO time of the last set/change/remove. */
43
+ updatedAt?: string;
44
+ }
45
+ export interface EmailTechPinView {
46
+ ok: boolean;
47
+ }
48
+ /** A developer waiver: send `waiver` in `EMAIL_WAIVER_HEADER` until `expiresAt` (ISO time). */
49
+ export interface EmailTechWaiverView {
50
+ waiver: string;
51
+ expiresAt: string;
52
+ }
53
+ /** A body with no fields (decline, waiver). */
54
+ export type EmailEmptyRequest = Record<string, never>;
55
+ /** `POST pin` — mail a sign-in PIN; the same answer for a known and a new address. */
56
+ export interface EmailPinRequest {
57
+ email: string;
58
+ locale?: string;
59
+ captcha?: string;
60
+ }
61
+ /**
62
+ * `POST sign-in` — ONE body for both modes: PIN mode sends `challenge` + `pin`, password mode
63
+ * sends `password` (+ `captcha`). The server accepts exactly one mode.
64
+ */
65
+ export interface EmailSignInRequest {
66
+ email: string;
67
+ /** The browser's device id (`EMAIL_DEVICE_ID_PATTERN`). */
68
+ device: string;
69
+ challenge?: string;
70
+ pin?: string;
71
+ password?: string;
72
+ captcha?: string;
73
+ locale?: string;
74
+ }
75
+ /** `POST password/pin` — mail a confirmation PIN to the account's own address. */
76
+ export interface EmailPasswordPinRequest {
77
+ purpose: EmailPasswordPurpose;
78
+ locale?: string;
79
+ }
80
+ /**
81
+ * `POST password/set` — confirmed either by the `offer` ticket of a fresh PIN sign-in or by a
82
+ * `challenge` + `pin` from `password/pin`. `device` keeps the current browser remembered while the
83
+ * account's other devices are forgotten.
84
+ */
85
+ export interface EmailPasswordSetRequest {
86
+ password: string;
87
+ offer?: string;
88
+ challenge?: string;
89
+ pin?: string;
90
+ device?: string;
91
+ }
92
+ /** `POST password/remove` — confirmed by a `challenge` + `pin` from `password/pin`. */
93
+ export interface EmailPasswordRemoveRequest {
94
+ challenge: string;
95
+ pin: string;
96
+ device?: string;
97
+ }
98
+ /** `POST tech/pin` — a developer replaces the PIN of a pending challenge. */
99
+ export interface EmailTechPinRequest {
100
+ challenge: string;
101
+ pin: string;
102
+ }
103
+ export interface EmailAuthGateOption {
104
+ alias: string;
105
+ params?: string | readonly string[];
106
+ }
107
+ /**
108
+ * The guest routes (`options`, `pin`, `sign-in`) are never guarded by this factory. The password
109
+ * routes need the application's session: `guards` (+ `gate`), or a `passwordParent` whose guards
110
+ * they inherit — neither is a declaration error. The developer (tech) routes default to
111
+ * `GUARD_ED25519` + the `EMAIL_AUTH_TECH_GATE` gate.
112
+ */
113
+ export interface EmailAuthEntrypointOptions {
114
+ /** The guest base path; default `EMAIL_AUTH_API_PATH`. */
115
+ path?: string;
116
+ /** The guest base's parent route. */
117
+ parent?: RouteParent;
118
+ /** Guards of the password subtree. */
119
+ guards?: string | readonly string[];
120
+ /** Gate of the password subtree. */
121
+ gate?: EmailAuthGateOption;
122
+ /** Mount the password subtree (`/password`) under this route instead of the guest base. */
123
+ passwordParent?: RouteParent;
124
+ /** Guards of the tech subtree; default `[GUARD_ED25519]`. */
125
+ techGuards?: string | readonly string[];
126
+ /** Gate of the tech subtree; default `{ alias: EMAIL_AUTH_TECH_GATE }`. */
127
+ techGate?: EmailAuthGateOption;
128
+ /** The offer screen; default path `EMAIL_AUTH_OFFER_PATH`, top-level. */
129
+ screen?: {
130
+ path?: string;
131
+ parent?: RouteParent;
132
+ };
133
+ }
134
+ export interface EmailPasswordEntrypoints {
135
+ base: EntrypointProtocol<OpenRequest, OpenValue>;
136
+ status: EntrypointProtocol<{}, EmailPasswordStatusView>;
137
+ pin: EntrypointProtocol<{
138
+ body: EmailPasswordPinRequest;
139
+ }, EmailChallengeView>;
140
+ set: EntrypointProtocol<{
141
+ body: EmailPasswordSetRequest;
142
+ }, EmailPasswordStatusView>;
143
+ remove: EntrypointProtocol<{
144
+ body: EmailPasswordRemoveRequest;
145
+ }, EmailPasswordStatusView>;
146
+ decline: EntrypointProtocol<{
147
+ body: EmailEmptyRequest;
148
+ }, EmailPasswordStatusView>;
149
+ }
150
+ export interface EmailTechEntrypoints {
151
+ base: EntrypointProtocol<OpenRequest, OpenValue>;
152
+ pin: EntrypointProtocol<{
153
+ body: EmailTechPinRequest;
154
+ }, EmailTechPinView>;
155
+ waiver: EntrypointProtocol<{
156
+ body: EmailEmptyRequest;
157
+ }, EmailTechWaiverView>;
158
+ }
159
+ export interface EmailAuthEntrypoints {
160
+ base: EntrypointProtocol<OpenRequest, OpenValue>;
161
+ options: EntrypointProtocol<{}, EmailAuthOptionsView>;
162
+ pin: EntrypointProtocol<{
163
+ body: EmailPinRequest;
164
+ }, EmailChallengeView>;
165
+ signIn: EntrypointProtocol<{
166
+ body: EmailSignInRequest;
167
+ }, EmailSignInView>;
168
+ password: EmailPasswordEntrypoints;
169
+ tech: EmailTechEntrypoints;
170
+ /** The post-sign-in "set a password" screen — frontend, sticky. */
171
+ offer: EntrypointProtocol<OpenRequest, OpenValue>;
172
+ }
173
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@owlmeans/auth-email",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.19",
4
+ "license": "MIT",
5
+ "description": "Contracts of OwlMeans e-mail sign-in — PIN, optional password, remembered devices, captcha: the protocol factory, schemas, views, constants and errors.",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsc -b",
9
+ "dev": "sleep 2 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
10
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
11
+ "test": "bun test ./tests"
12
+ },
13
+ "main": "build/index.js",
14
+ "module": "build/index.js",
15
+ "types": "build/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./build/index.js",
19
+ "require": "./build/index.js",
20
+ "default": "./build/index.js",
21
+ "module": "./build/index.js",
22
+ "types": "./build/index.d.ts"
23
+ }
24
+ },
25
+ "dependencies": {
26
+ "@owlmeans/auth": "^0.1.19",
27
+ "@owlmeans/auth-common": "^0.1.19",
28
+ "@owlmeans/context": "^0.1.19",
29
+ "@owlmeans/entrypoint": "^0.1.19",
30
+ "@owlmeans/error": "^0.1.19",
31
+ "@owlmeans/route": "^0.1.19"
32
+ },
33
+ "peerDependencies": {
34
+ "ajv": "*",
35
+ "ajv-formats": "*"
36
+ },
37
+ "devDependencies": {
38
+ "@owlmeans/dep-config": "^0.1.19",
39
+ "@types/bun": "^1.4.2",
40
+ "ajv": "^8.20.0",
41
+ "ajv-formats": "^3.0.1",
42
+ "nodemon": "^3.1.14",
43
+ "typescript": "^7.0.2"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }
package/src/consts.ts ADDED
@@ -0,0 +1,107 @@
1
+ // --- Methods, auth types and envelopes ---------------------------------------------------------
2
+
3
+ /** The client `AuthenticationPlugin` type and the login method id of e-mail sign-in. */
4
+ export const EMAIL_LOGIN_METHOD = 'email'
5
+
6
+ /** The private server plugin of a PIN sign-in, and the type its signed token carries. */
7
+ export const EMAIL_PIN_AUTH_TYPE = 'email-pin'
8
+
9
+ /** The private server plugin of a password sign-in, and the type its signed token carries. */
10
+ export const EMAIL_PASSWORD_AUTH_TYPE = 'email-password'
11
+
12
+ /** A purpose-bound confirmation challenge (set, change or remove a password). */
13
+ export const EMAIL_CONFIRM_ENVELOPE = 'email-confirm'
14
+
15
+ /** A developer's captcha and throttle waiver. */
16
+ export const EMAIL_WAIVER_ENVELOPE = 'email-waiver'
17
+
18
+ /** The request header that carries an `EMAIL_WAIVER_ENVELOPE`. */
19
+ export const EMAIL_WAIVER_HEADER = 'x-owlmeans-email-waiver'
20
+
21
+ /** The captcha action of a send-PIN request. */
22
+ export const EMAIL_CAPTCHA_ACTION_PIN = 'email_pin'
23
+
24
+ /** The captcha action of a password sign-in. */
25
+ export const EMAIL_CAPTCHA_ACTION_PASSWORD = 'email_password'
26
+
27
+ /**
28
+ * The identity-linking details type of an account's e-mail credential — the same value as
29
+ * `OTP_AUTH_TYPE` (`@owlmeans/auth-otp`), so e-mail sign-in and e-mail OTP land on one account.
30
+ * It names a credential, never a token type.
31
+ */
32
+ export const EMAIL_IDENTITY_TYPE = 'email-otp'
33
+
34
+ /** The captcha provider an options view may name. */
35
+ export const EMAIL_CAPTCHA_PROVIDER = 'recaptcha-v3'
36
+
37
+ // --- Policy ------------------------------------------------------------------------------------
38
+
39
+ /** Digits in a PIN — the same as `OTP_CODE_LENGTH` (`@owlmeans/auth-otp`). */
40
+ export const EMAIL_PIN_LENGTH = 6
41
+ export const EMAIL_PIN_PATTERN = '^[0-9]{6}$'
42
+
43
+ /** How long a device stays remembered after a successful sign-in on it. */
44
+ export const EMAIL_DEVICE_TTL_DAYS = 30
45
+ /** Remembered devices per account; the oldest beyond it are forgotten. */
46
+ export const EMAIL_DEVICE_LIMIT = 10
47
+ /** A device id: 22 base64url characters (16 random bytes) from the client, up to 64 accepted. */
48
+ export const EMAIL_DEVICE_ID_PATTERN = '^[A-Za-z0-9_-]{22,64}$'
49
+
50
+ /** Lifetime of the one-use "set a password" offer ticket a PIN sign-in returns. */
51
+ export const EMAIL_OFFER_TTL_SECONDS = 900
52
+ /** Lifetime of a developer waiver. */
53
+ export const EMAIL_WAIVER_TTL_SECONDS = 7200
54
+
55
+ export const EMAIL_PASSWORD_MIN_LENGTH = 8
56
+ export const EMAIL_PASSWORD_MAX_LENGTH = 128
57
+
58
+ /** Wire bounds of the request fields (the password policy above is checked by the server). */
59
+ export const EMAIL_ADDRESS_MAX_LENGTH = 254
60
+ export const EMAIL_PASSWORD_WIRE_MAX_LENGTH = 1024
61
+ export const EMAIL_TOKEN_MAX_LENGTH = 4096
62
+ export const EMAIL_LOCALE_MAX_LENGTH = 16
63
+
64
+ /** The languages the sign-in mails are written in; any other locale falls back to `en`. */
65
+ export const EMAIL_AUTH_LOCALES = Object.freeze(['en', 'pl', 'ru', 'be', 'uk', 'es', 'de', 'fr'] as const)
66
+
67
+ export enum EmailPasswordPurpose {
68
+ Set = 'set',
69
+ Change = 'change',
70
+ Remove = 'remove',
71
+ }
72
+
73
+ export enum EmailSignInReason {
74
+ /** The password was not checked: the device is unknown, so a PIN was mailed instead. */
75
+ Device = 'device',
76
+ }
77
+
78
+ // --- Service aliases ---------------------------------------------------------------------------
79
+
80
+ /** The server service alias. */
81
+ export const EMAIL_AUTH_SERVICE = 'auth-email'
82
+ /** The client service alias. */
83
+ export const EMAIL_LOGIN_SERVICE = 'auth-email-client'
84
+ /** The gate service standing over the developer (tech) routes. */
85
+ export const EMAIL_AUTH_TECH_GATE = 'auth-email:tech-gate'
86
+
87
+ // --- Paths -------------------------------------------------------------------------------------
88
+
89
+ export const EMAIL_AUTH_API_PATH = '/auth/email'
90
+ export const EMAIL_AUTH_OFFER_PATH = '/login/password'
91
+
92
+ // --- Protocol tree aliases ---------------------------------------------------------------------
93
+
94
+ export const EMAIL_AUTH_BASE = 'auth-email:base'
95
+ export const EMAIL_AUTH_OPTIONS = 'auth-email:options'
96
+ export const EMAIL_AUTH_PIN = 'auth-email:pin'
97
+ export const EMAIL_AUTH_SIGN_IN = 'auth-email:sign-in'
98
+ export const EMAIL_AUTH_PASSWORD = 'auth-email:password'
99
+ export const EMAIL_AUTH_PASSWORD_STATUS = 'auth-email:password:status'
100
+ export const EMAIL_AUTH_PASSWORD_PIN = 'auth-email:password:pin'
101
+ export const EMAIL_AUTH_PASSWORD_SET = 'auth-email:password:set'
102
+ export const EMAIL_AUTH_PASSWORD_REMOVE = 'auth-email:password:remove'
103
+ export const EMAIL_AUTH_PASSWORD_DECLINE = 'auth-email:password:decline'
104
+ export const EMAIL_AUTH_TECH = 'auth-email:tech'
105
+ export const EMAIL_AUTH_TECH_PIN = 'auth-email:tech:pin'
106
+ export const EMAIL_AUTH_TECH_WAIVER = 'auth-email:tech:waiver'
107
+ export const EMAIL_AUTH_OFFER = 'auth-email:offer'