@webda/auth 0.0.0-stage → 4.0.0-beta.11

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 (77) hide show
  1. package/LICENSE +166 -0
  2. package/README.md +59 -2
  3. package/lib/authentication.service.d.ts +322 -0
  4. package/lib/authentication.service.d.ts.map +1 -0
  5. package/lib/authentication.service.js +978 -0
  6. package/lib/authentication.service.js.map +1 -0
  7. package/lib/certificate/certificate.service.d.ts +139 -0
  8. package/lib/certificate/certificate.service.d.ts.map +1 -0
  9. package/lib/certificate/certificate.service.js +343 -0
  10. package/lib/certificate/certificate.service.js.map +1 -0
  11. package/lib/compat/upgrade.d.ts +71 -0
  12. package/lib/compat/upgrade.d.ts.map +1 -0
  13. package/lib/compat/upgrade.js +200 -0
  14. package/lib/compat/upgrade.js.map +1 -0
  15. package/lib/email/emailpassword.service.d.ts +249 -0
  16. package/lib/email/emailpassword.service.d.ts.map +1 -0
  17. package/lib/email/emailpassword.service.js +714 -0
  18. package/lib/email/emailpassword.service.js.map +1 -0
  19. package/lib/email/tokens.d.ts +36 -0
  20. package/lib/email/tokens.d.ts.map +1 -0
  21. package/lib/email/tokens.js +46 -0
  22. package/lib/email/tokens.js.map +1 -0
  23. package/lib/errors.d.ts +53 -0
  24. package/lib/errors.d.ts.map +1 -0
  25. package/lib/errors.js +74 -0
  26. package/lib/errors.js.map +1 -0
  27. package/lib/index.d.ts +12 -0
  28. package/lib/index.d.ts.map +1 -0
  29. package/lib/index.js +12 -0
  30. package/lib/index.js.map +1 -0
  31. package/lib/mfa/secondfactor.service.d.ts +44 -0
  32. package/lib/mfa/secondfactor.service.d.ts.map +1 -0
  33. package/lib/mfa/secondfactor.service.js +38 -0
  34. package/lib/mfa/secondfactor.service.js.map +1 -0
  35. package/lib/mfa/totp.service.d.ts +41 -0
  36. package/lib/mfa/totp.service.d.ts.map +1 -0
  37. package/lib/mfa/totp.service.js +63 -0
  38. package/lib/mfa/totp.service.js.map +1 -0
  39. package/lib/oauth/oauth.service.d.ts +296 -0
  40. package/lib/oauth/oauth.service.d.ts.map +1 -0
  41. package/lib/oauth/oauth.service.js +560 -0
  42. package/lib/oauth/oauth.service.js.map +1 -0
  43. package/lib/provider.d.ts +53 -0
  44. package/lib/provider.d.ts.map +1 -0
  45. package/lib/provider.js +55 -0
  46. package/lib/provider.js.map +1 -0
  47. package/lib/test/authtest.d.ts +39 -0
  48. package/lib/test/authtest.d.ts.map +1 -0
  49. package/lib/test/authtest.js +62 -0
  50. package/lib/test/authtest.js.map +1 -0
  51. package/lib/test/emailtest.d.ts +25 -0
  52. package/lib/test/emailtest.d.ts.map +1 -0
  53. package/lib/test/emailtest.js +42 -0
  54. package/lib/test/emailtest.js.map +1 -0
  55. package/lib/test/fakeoauth.service.d.ts +45 -0
  56. package/lib/test/fakeoauth.service.d.ts.map +1 -0
  57. package/lib/test/fakeoauth.service.js +78 -0
  58. package/lib/test/fakeoauth.service.js.map +1 -0
  59. package/lib/test/mfauser.fixture.model.d.ts +11 -0
  60. package/lib/test/mfauser.fixture.model.d.ts.map +1 -0
  61. package/lib/test/mfauser.fixture.model.js +44 -0
  62. package/lib/test/mfauser.fixture.model.js.map +1 -0
  63. package/lib/test/stubprovider.d.ts +35 -0
  64. package/lib/test/stubprovider.d.ts.map +1 -0
  65. package/lib/test/stubprovider.js +46 -0
  66. package/lib/test/stubprovider.js.map +1 -0
  67. package/lib/test/v3.d.ts +29 -0
  68. package/lib/test/v3.d.ts.map +1 -0
  69. package/lib/test/v3.js +47 -0
  70. package/lib/test/v3.js.map +1 -0
  71. package/lib/throttle.d.ts +41 -0
  72. package/lib/throttle.d.ts.map +1 -0
  73. package/lib/throttle.js +49 -0
  74. package/lib/throttle.js.map +1 -0
  75. package/package.json +69 -3
  76. package/test/config.json +23 -0
  77. package/webda.module.json +891 -0
@@ -0,0 +1,200 @@
1
+ import { Ident } from "@webda/core";
2
+ import { IdentConflict } from "../errors.js";
3
+ /**
4
+ * v3 ident records
5
+ *
6
+ * v3 stored idents under `"<providerUid>_<provider>"` (plain JSON, `__type: "Webda/Ident"`, `uuid` kept in the
7
+ * body). The core `Ident` repository addresses and queries them through `Ident.parseLegacyUID` (a model-level
8
+ * legacy key hook): they load as `Ident` instances without `providerUid`, recognised by `ident.getLegacyUID()`.
9
+ * Nothing else may use such an instance: it must go through {@link upgradeIdent} first.
10
+ */
11
+ /**
12
+ * @param providerUid - uid on the provider
13
+ * @param provider - provider name
14
+ * @returns the v3 key `"<providerUid>_<provider>"`
15
+ */
16
+ export function legacyKey(providerUid, provider) {
17
+ return `${providerUid}_${provider}`;
18
+ }
19
+ /**
20
+ * Split a v3 key on its LAST "_" (provider names never contain "_", emails may)
21
+ * @param uuid - v3 key
22
+ * @returns the parts, or undefined when not a v3 key
23
+ */
24
+ export function splitLegacyKey(uuid) {
25
+ return Ident.parseLegacyUID(uuid);
26
+ }
27
+ /**
28
+ * @param value - a v3 date (ISO string, timestamp or Date)
29
+ * @returns the date, undefined when absent or unreadable
30
+ */
31
+ function toDate(value) {
32
+ if (value === undefined || value === null || value === "" || value === false) {
33
+ return undefined;
34
+ }
35
+ const date = value instanceof Date ? value : new Date(value);
36
+ return isNaN(date.getTime()) ? undefined : date;
37
+ }
38
+ /**
39
+ * @param value - a v3 timestamp (number, ISO string or Date)
40
+ * @returns the timestamp in ms, undefined when absent or unreadable
41
+ */
42
+ function toTimestamp(value) {
43
+ if (typeof value === "number") {
44
+ return value || undefined;
45
+ }
46
+ return toDate(value)?.getTime();
47
+ }
48
+ /**
49
+ * Whether a row loaded under a v3 key really is a v3 ident of this application
50
+ *
51
+ * The row must be an instance of the ident model, carry a v3 key and be stored untyped, as `Webda/Ident`, or as
52
+ * the ident model, one of its ancestors or transitive subclasses: a v3-looking key of a shared store may hold
53
+ * another model's row.
54
+ * @param row - the loaded row
55
+ * @param IdentModel - ident model of the application
56
+ * @returns true for a v3 ident
57
+ */
58
+ export function isLegacyIdent(row, IdentModel = Ident) {
59
+ if (!(row instanceof IdentModel) || !row.getLegacyUID()) {
60
+ return false;
61
+ }
62
+ const type = row.__type;
63
+ return type === undefined || identIdentifiers(IdentModel).has(type) || rowClassIdentifiers(row).has(type);
64
+ }
65
+ /**
66
+ * Identifiers of the class a row loaded as and of its ancestors: a subclass of the ident model defined by another
67
+ * package (e.g. an application `MyApp/Ident`) is not listed in the ident model `Metadata.Subclasses`, yet a row
68
+ * typed as it loads as it
69
+ * @param row - the loaded row, an instance of the ident model
70
+ * @returns the identifiers
71
+ */
72
+ function rowClassIdentifiers(row) {
73
+ const ids = new Set();
74
+ for (let clazz = row?.constructor; clazz && clazz !== Function.prototype; clazz = Object.getPrototypeOf(clazz)) {
75
+ if (clazz.Metadata?.Identifier)
76
+ ids.add(clazz.Metadata.Identifier);
77
+ }
78
+ return ids;
79
+ }
80
+ /**
81
+ * Identifiers a v3 ident row of this ident model may be stored as: `Webda/Ident`, the model, its ancestors and
82
+ * its transitive subclasses
83
+ * @param IdentModel - ident model of the application
84
+ * @returns the identifiers
85
+ */
86
+ function identIdentifiers(IdentModel) {
87
+ const ids = new Set(["Webda/Ident"]);
88
+ for (let parent = Object.getPrototypeOf(IdentModel); parent && parent !== Function.prototype;) {
89
+ if (parent.Metadata?.Identifier)
90
+ ids.add(parent.Metadata.Identifier);
91
+ parent = Object.getPrototypeOf(parent);
92
+ }
93
+ const queue = [IdentModel];
94
+ const seen = new Set();
95
+ while (queue.length) {
96
+ const clazz = queue.shift();
97
+ if (!clazz || seen.has(clazz))
98
+ continue;
99
+ seen.add(clazz);
100
+ if (clazz.Metadata?.Identifier)
101
+ ids.add(clazz.Metadata.Identifier);
102
+ queue.push(...(clazz.Metadata?.Subclasses ?? []));
103
+ }
104
+ return ids;
105
+ }
106
+ /**
107
+ * Upgrade a v3 ident: create the `"<providerUid>:<provider>"` record, then delete the v3 one
108
+ *
109
+ * Idempotent and crash-safe: when the new record already exists (an earlier upgrade stopped before the delete,
110
+ * or a concurrent one won) it is kept as is and only the v3 record is deleted. Field map: `_user` → owner,
111
+ * `_validation` → `verifiedAt`, `_lastUsed` → `lastUsedAt`, `_failedLogin` → `_loginAttempts`,
112
+ * `_lastFailedLogin` → `_lastLoginAttemptAt` (number or date; absent, the count is treated as an expired lock),
113
+ * `_lastValidationEmail` → `_throttle.lastSentAt`; `email`, `__profile` are kept, `__tokens` is encrypted into `tokens`; provider is
114
+ * `provider ?? _type ?? key suffix`, providerUid the key prefix (normalised for the email provider: v3 kept the
115
+ * email case). Callers run it as system.
116
+ * @throws Error when `legacy` is not a v3 ident (see {@link isLegacyIdent})
117
+ * @throws IdentConflict when the upgraded key exists, or is created concurrently, with another owner: the v3
118
+ * record is kept for a manual resolution
119
+ * @param legacy - a v3 ident (`legacy.getLegacyUID()` is set)
120
+ * @param IdentModel - ident model of the application
121
+ * @param options - options
122
+ * @param options.dryRun - run the checks only: nothing is written, the returned ident is not saved
123
+ * @returns the upgraded ident
124
+ */
125
+ export async function upgradeIdent(legacy, IdentModel = Ident, options = {}) {
126
+ const uid = isLegacyIdent(legacy, IdentModel) ? legacy.getLegacyUID() : undefined;
127
+ const parts = uid ? splitLegacyKey(uid) : undefined;
128
+ if (!parts) {
129
+ throw new Error("Not a v3 ident");
130
+ }
131
+ const v3 = legacy;
132
+ const provider = v3.provider ?? v3._type ?? parts.provider;
133
+ // v3 did not normalise emails: an email key is upgraded under its normalised (lowercase) form
134
+ const providerUid = provider === "email" ? Ident.normalizeEmail(parts.providerUid) : parts.providerUid;
135
+ const key = Ident.key(providerUid, provider);
136
+ const ref = IdentModel.ref(key);
137
+ const owner = v3._user?.toString();
138
+ // Another account may already hold the upgraded key (e.g. two v3 keys differing only by case)
139
+ const checkOwner = (existing) => {
140
+ if (existing.getUser()?.toString() !== owner)
141
+ throw new IdentConflict();
142
+ return existing;
143
+ };
144
+ let ident = (await ref.exists()) ? checkOwner(await ref.get()) : undefined;
145
+ if (!ident) {
146
+ const created = new IdentModel({
147
+ ...key,
148
+ email: v3.email ?? (provider === "email" ? providerUid : undefined),
149
+ verifiedAt: v3._validation ? (toDate(v3._validation) ?? new Date()) : undefined,
150
+ lastUsedAt: toDate(v3._lastUsed),
151
+ _throttle: { lastSentAt: toTimestamp(v3._lastValidationEmail) },
152
+ _loginAttempts: typeof v3._failedLogin === "number" ? v3._failedLogin : 0,
153
+ _lastLoginAttemptAt: toTimestamp(v3._lastFailedLogin),
154
+ __profile: v3.__profile
155
+ });
156
+ if (owner) {
157
+ created.setUser(owner);
158
+ }
159
+ // v3 kept plaintext tokens: they are only stored encrypted
160
+ if (v3.__tokens && !options.dryRun) {
161
+ await created.tokens.set(v3.__tokens);
162
+ }
163
+ if (options.dryRun) {
164
+ return created;
165
+ }
166
+ try {
167
+ ident = await created.getRepository().create(created);
168
+ }
169
+ catch (err) {
170
+ // Store-agnostic lost race: the record now exists, whatever error the store reported
171
+ if (!(await ref.exists()))
172
+ throw err;
173
+ // The race may have been won by another v3 record of another user: never delete this one then
174
+ ident = checkOwner(await ref.get());
175
+ }
176
+ }
177
+ if (!options.dryRun) {
178
+ await IdentModel.ref(uid).delete();
179
+ }
180
+ return ident;
181
+ }
182
+ /**
183
+ * Enumerate the v3 idents of a store, for a batch migration (`webda auth migrate`)
184
+ *
185
+ * Iterates the ident model (all idents, the v3 ones are filtered client side: they lack `providerUid`, which
186
+ * stores cannot portably query for) and yields only the v3 records. Pass each to {@link upgradeIdent}.
187
+ * Upgrading deletes records, which shifts offset-based pagination (memory and file stores page by position):
188
+ * a migration upgrading while iterating must run passes until one yields nothing (upgrades are idempotent).
189
+ * @param IdentModel - ident model of the application
190
+ * @param query - optional WebdaQL filter, e.g. `"_user = 'uid'"`
191
+ * @returns the v3 idents
192
+ */
193
+ export async function* legacyIdents(IdentModel = Ident, query = "") {
194
+ for await (const ident of IdentModel.iterate(query)) {
195
+ if (ident.getLegacyUID()) {
196
+ yield ident;
197
+ }
198
+ }
199
+ }
200
+ //# sourceMappingURL=upgrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/compat/upgrade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,249 @@
1
+ import { type AuthResult, Ident, type ProviderInfo, Service, ServiceParameters, type WebContext } from "@webda/core";
2
+ import type { AuthProvider } from "../provider.js";
3
+ /** When the email is verified relative to account creation */
4
+ export type VerificationMode = "before" | "after" | "none";
5
+ /** EmailPasswordProvider parameters */
6
+ export declare class EmailPasswordParameters extends ServiceParameters {
7
+ /**
8
+ * Mailer service name
9
+ * @default "Mailer"
10
+ */
11
+ mailer: string;
12
+ /**
13
+ * Verification mode
14
+ * @default "before"
15
+ */
16
+ verification: VerificationMode;
17
+ /** Password rules
18
+ * @default { "policy": ".{8,}" }
19
+ */
20
+ password: {
21
+ /** @default ".{8,}" */
22
+ policy: string;
23
+ verifier?: string;
24
+ };
25
+ /** Throttling
26
+ * @default { "resendDelay": 14400000, "failedBeforeDelay": 3, "lockout": 900000 }
27
+ */
28
+ throttle: {
29
+ /** @default 14400000 */
30
+ resendDelay: number;
31
+ /** @default 3 */
32
+ failedBeforeDelay: number;
33
+ /** @default 900000 */
34
+ lockout: number;
35
+ };
36
+ /**
37
+ * Browser redirects for emailed links (no defaults: init() refuses a missing required one). `failure` and `recover`
38
+ * are always required, `verified` and `register` unless `verification` is "none". `recover` receives the recovery
39
+ * link as `?token=...` (the page then calls Auth.Password.Recover). `confirm` (default `failure`) receives a
40
+ * verification link opened without the matching session, with `?reason=LOGIN_REQUIRED&token=...`: log in then call
41
+ * Auth.Email.Verify with the token
42
+ */
43
+ redirects: {
44
+ verified?: string;
45
+ failure?: string;
46
+ register?: string;
47
+ confirm?: string;
48
+ recover?: string;
49
+ };
50
+ /**
51
+ * Base url for emailed links
52
+ * @default "/auth/email"
53
+ */
54
+ url: string;
55
+ /** Only these email domains may use this provider (see ProviderEmailPolicy) */
56
+ allowedEmailDomains?: string[];
57
+ /** Whether the verification of the email is believed (see ProviderEmailPolicy) */
58
+ trustEmailVerification?: boolean | string[];
59
+ /**
60
+ * @param params - raw parameters
61
+ * @returns this
62
+ */
63
+ load(params?: any): this;
64
+ }
65
+ /**
66
+ * Email + password login method
67
+ *
68
+ * The password policy is process-wide: resolve() registers the one of this provider and stop() restores the
69
+ * default. With several instances the last one resolved wins.
70
+ *
71
+ * Known residual exposure: Auth.Email.Register answers AccountExists for a registered email (enumeration), and
72
+ * login timing differs slightly for known emails. An unverified squatter owning an email blocks its registration
73
+ * until the real owner goes through account recovery.
74
+ *
75
+ * Under `allowedEmailDomains` an email must be verified: with `verification` "after" or "none" a new registration is
76
+ * unverified and therefore refused (EMAIL_DOMAIN_NOT_ALLOWED); use "before".
77
+ * @WebdaModda EmailPasswordProvider
78
+ */
79
+ export declare class EmailPasswordProvider<T extends EmailPasswordParameters = EmailPasswordParameters> extends Service<T> implements AuthProvider {
80
+ static Parameters: typeof EmailPasswordParameters;
81
+ readonly providerName = "email";
82
+ /** @override */
83
+ resolve(): this;
84
+ /** @override */
85
+ init(): Promise<this>;
86
+ /** @override */
87
+ stop(): Promise<void>;
88
+ /**
89
+ * @returns public info
90
+ */
91
+ getPublicInfo(): ProviderInfo;
92
+ /**
93
+ * @param email - normalised email
94
+ * @returns the email ident
95
+ */
96
+ protected getIdent(email: string): Promise<Ident | undefined>;
97
+ /**
98
+ * Absolute link for an emailed token
99
+ * @param path - path under the provider url
100
+ * @param token - token
101
+ * @returns url
102
+ */
103
+ buildLink(path: string, token: string): string;
104
+ /**
105
+ * @param template - mail template
106
+ * @param to - recipient
107
+ * @param url - link
108
+ * @param token - token
109
+ */
110
+ protected sendMail(template: "EMAIL_REGISTER" | "EMAIL_RECOVERY", to: string, url: string, token: string): Promise<void>;
111
+ /**
112
+ * Count a login attempt before verifying it and decide whether the ident is locked
113
+ *
114
+ * Rule, equivalent to `isLocked(ident, failedBeforeDelay, lockout)` on the state before this attempt:
115
+ * `_loginAttempts` is incremented first (atomically; the new value is returned by the memory repository and
116
+ * re-read otherwise), so this attempt is locked when the count of PREVIOUS attempts (`_loginAttempts - 1`) is at
117
+ * least `failedBeforeDelay`, unless that lock has expired, ie `_lastLoginAttemptAt` as read when this call started
118
+ * is older than `lockout`. `_lastLoginAttemptAt` is only refreshed by attempts that are not refused, so hammering
119
+ * a locked ident does not extend the lock; a count reaching the threshold without timestamp is an expired lock.
120
+ * Both are top-level attributes: stores implement atomic increment and
121
+ * single attribute set on them (nested-path atomic operations are not portable across stores). The timestamp
122
+ * is a plain set (last writer wins); the counter is never written back from a snapshot. A burst starting exactly
123
+ * when an expired lock is read is verified as a whole: it is bounded to one burst per lock window.
124
+ * @param ident - the ident
125
+ * @returns true when this attempt must be refused without checking the password
126
+ */
127
+ protected countAttempt(ident: Ident): Promise<boolean>;
128
+ /**
129
+ * Login with email and password
130
+ * @param email - email
131
+ * @param password - password
132
+ * @returns the auth result
133
+ */
134
+ login(email: string, password: string): Promise<AuthResult>;
135
+ /**
136
+ * Register with email and password
137
+ * @param email - email
138
+ * @param password - password
139
+ * @param token - register token from the emailed link (verification "before")
140
+ * @param profile - extra user fields
141
+ * @returns the auth result, or verification_sent
142
+ */
143
+ register(email: string, password: string, token?: string, profile?: any): Promise<any>;
144
+ /**
145
+ * Send (or resend) a verification link
146
+ *
147
+ * Logged in: the link proves the email for the current account (`sub` is only in the token, the ident stays
148
+ * unowned until Auth.Email.Verify is called by the same session). Logged out: always answers with no content and
149
+ * never reveals anything; a link is only sent for an unowned or unknown email.
150
+ * @param email - email
151
+ */
152
+ startVerification(email: string): Promise<void>;
153
+ /**
154
+ * Logged-out link: a register link is emailed, without awaiting the mail, only for an unknown or unowned and
155
+ * unverified email whose send throttle allows it; anything else is silently ignored
156
+ * @param normalized - normalised email
157
+ */
158
+ protected sendUnownedLink(normalized: string): Promise<void>;
159
+ /**
160
+ * Check and mark the send throttle of an email ident, then sign the token to email; runs as system
161
+ * @param normalized - normalised email
162
+ * @param userId - logged user (verify token), undefined for a logged-out register token
163
+ * @returns the token to send, undefined when nothing must be sent (logged out only)
164
+ * @throws IdentLinkedElsewhere / PreconditionFailed / Throttled (logged in only)
165
+ */
166
+ protected prepareLink(normalized: string, userId: string | undefined): Promise<string | undefined>;
167
+ /**
168
+ * Send a mail without awaiting it; failures are logged without any secret
169
+ * @param template - mail template
170
+ * @param to - recipient
171
+ * @param url - link
172
+ * @param token - token
173
+ */
174
+ protected sendMailDetached(template: "EMAIL_REGISTER" | "EMAIL_RECOVERY", to: string, url: string, token: string): void;
175
+ /** Mails sent without being awaited */
176
+ protected pendingMails: Set<Promise<void>>;
177
+ /**
178
+ * Wait for the mails sent without being awaited (logged out verification, recovery)
179
+ */
180
+ flushMails(): Promise<void>;
181
+ /**
182
+ * Mark an email as verified: only the session of the user named by the token can complete it
183
+ * @param token - verify token
184
+ * @returns status
185
+ */
186
+ verify(token: string): Promise<{
187
+ status: "verified";
188
+ }>;
189
+ /**
190
+ * Verify with an explicit context
191
+ * @param token - verify token
192
+ * @param ctx - context whose session must match the token
193
+ * @returns status
194
+ */
195
+ protected verifyWith(token: string, ctx: any): Promise<{
196
+ status: "verified";
197
+ }>;
198
+ /**
199
+ * @param claims - verified token claims
200
+ * @param claims.sub - user id
201
+ * @param ctx - context
202
+ * @returns true when the session is logged as the user of the token
203
+ */
204
+ protected sessionMatches(claims: {
205
+ sub?: string;
206
+ }, ctx: any): boolean;
207
+ /**
208
+ * Complete a verification; nothing changes unless every check passes
209
+ * @param claims - verified token claims
210
+ * @param claims.email - email of the token
211
+ * @param claims.sub - user id
212
+ * @param ctx - context
213
+ */
214
+ protected completeVerify(claims: {
215
+ email: string;
216
+ sub?: string;
217
+ }, ctx: any): Promise<void>;
218
+ /**
219
+ * Start password recovery; always succeeds to avoid revealing accounts
220
+ * @param email - email
221
+ */
222
+ startRecovery(email: string): Promise<void>;
223
+ /**
224
+ * Set a new password from a recovery link (does not log in): revokes the refresh tokens and ends every session of
225
+ * the user (see `Session.authAt`), resets the login attempts of the email ident
226
+ * @param token - recover token
227
+ * @param password - new password
228
+ */
229
+ recover(token: string, password: string): Promise<void>;
230
+ /**
231
+ * Change the current user's password: revokes the refresh tokens and ends the other sessions of the user, the
232
+ * calling cookie session is re-stamped and stays logged in
233
+ * @param current - current password
234
+ * @param next - new password
235
+ */
236
+ changePassword(current: string, next: string): Promise<void>;
237
+ /**
238
+ * Browser landing for recovery links: redirects a valid recover token to `redirects.recover` without consuming it,
239
+ * anything else to `redirects.failure`; never throws
240
+ * @param ctx - web context
241
+ */
242
+ recoverRedirect(ctx: WebContext): Promise<void>;
243
+ /**
244
+ * Browser landing for emailed links: verifies then redirects, never throws
245
+ * @param ctx - web context
246
+ */
247
+ verifyRedirect(ctx: WebContext): Promise<void>;
248
+ }
249
+ //# sourceMappingURL=emailpassword.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emailpassword.service.d.ts","sourceRoot":"","sources":["../../src/email/emailpassword.service.ts"],"names":[],"mappings":"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"}