@ultimabase/password 0.1.2 → 0.2.0
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.
- package/CHANGELOG.md +20 -0
- package/dist/core.d.ts +7 -3
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +16 -8
- package/dist/email-field.d.ts +35 -0
- package/dist/email-field.d.ts.map +1 -0
- package/dist/email-field.js +77 -0
- package/dist/email-functions.d.ts.map +1 -1
- package/dist/email-functions.js +48 -6
- package/dist/input.d.ts +12 -0
- package/dist/input.d.ts.map +1 -1
- package/dist/input.js +7 -0
- package/dist/models.d.ts +8 -4
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +22 -8
- package/dist/plugin.d.ts +14 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +62 -12
- package/dist/tokens.d.ts +29 -10
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +10 -5
- package/package.json +8 -8
- package/src/core.ts +24 -8
- package/src/email-field.ts +104 -0
- package/src/email-functions.ts +65 -8
- package/src/input.ts +26 -0
- package/src/models.ts +33 -10
- package/src/plugin.ts +94 -14
- package/src/tokens.ts +34 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @ultimabase/password
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f3a1fac: `User.checkPassword({ id, password }, { as, client })` checks the current password before a sensitive action, opens no session, and is counted per user even without a client. A wrong current password, here and in `changePassword`, now answers `Wrong password.`
|
|
8
|
+
- fcca799: `User.closeAccount({ id }, { as })` closes an account and keeps its row: it ends the user's sessions, unlinks their accounts, and runs the listeners sign-in methods register with `AuthApi.onCloseAccount`. password's deletes the hash, the tokens and the email requests, and sets `emailVerified` to `false`, so the email signs up again.
|
|
9
|
+
- 99cdfec: `password()` adds a unique `email` field to the user model, which the user and Master read and only Master writes; the password account follows it. `requestEmailChange` and `confirmEmailChange` change the email from a link sent to the new address, and `sendEmail` tells the old one with an `'emailChanged'` email. `AuthApi.unlinkAccount` unlinks one method's account.
|
|
10
|
+
|
|
11
|
+
Breaking: `Email` is a union, so read `token` after checking `kind`; a user model that declares its own `email` stops startup; users stored before this release read `email: null` until a Master `User.update({ where: { id }, data: { email } })` writes their account's email.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [fcca799]
|
|
16
|
+
- Updated dependencies [99cdfec]
|
|
17
|
+
- Updated dependencies [2c3bf4e]
|
|
18
|
+
- Updated dependencies [fd1263e]
|
|
19
|
+
- Updated dependencies [6d4f980]
|
|
20
|
+
- @ultimabase/auth@0.2.0
|
|
21
|
+
- ultimabase@0.2.0
|
|
22
|
+
|
|
3
23
|
## 0.1.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AuthApi } from '@ultimabase/auth';
|
|
2
2
|
import type { Client, ExposedModel, Logger, Row } from 'ultimabase';
|
|
3
|
+
import type { SendEmail } from './email-functions.ts';
|
|
3
4
|
import type { Attempt } from './models.ts';
|
|
4
5
|
/** The method `AuthAccount` links a password's email with (D-113). */
|
|
5
6
|
export declare const method = "password";
|
|
@@ -12,6 +13,7 @@ export interface State {
|
|
|
12
13
|
readonly logger: Logger;
|
|
13
14
|
/** The sends under way, which stopping waits for (D-117). */
|
|
14
15
|
readonly sending: Set<Promise<void>>;
|
|
16
|
+
readonly sendEmail: SendEmail | undefined;
|
|
15
17
|
}
|
|
16
18
|
/** What a sign-in returns: its session, and the user read as itself. */
|
|
17
19
|
export interface SignedIn<U = Row> {
|
|
@@ -28,7 +30,8 @@ export interface SignedIn<U = Row> {
|
|
|
28
30
|
export declare const attemptOf: (subject: string, client: Client | undefined) => Attempt;
|
|
29
31
|
/**
|
|
30
32
|
* Creates the user, its account and its hash in one transaction, as Master:
|
|
31
|
-
* the caller's fields were checked against the list (D-113).
|
|
33
|
+
* the caller's fields were checked against the list (D-113). The email's
|
|
34
|
+
* hook links the account (D-162).
|
|
32
35
|
*/
|
|
33
36
|
export declare function createUser(state: State, email: string, password: string, data: object): Promise<{
|
|
34
37
|
readonly user: Row;
|
|
@@ -44,8 +47,9 @@ export declare function verified(state: State, email: string, password: string,
|
|
|
44
47
|
/** Checks `userId`'s current password, counting the attempt. */
|
|
45
48
|
export declare function checkCurrent(userId: string, password: string, attempt: Attempt): Promise<void>;
|
|
46
49
|
/**
|
|
47
|
-
* Replaces `userId`'s hash,
|
|
48
|
-
* pending reset tokens (D-117);
|
|
50
|
+
* Replaces `userId`'s hash, or gives one to an email without (D-162), ends
|
|
51
|
+
* every session of the user and deletes its pending reset tokens (D-117);
|
|
52
|
+
* the new stamp.
|
|
49
53
|
*/
|
|
50
54
|
export declare function setPassword(state: State, userId: string, password: string): Promise<string>;
|
|
51
55
|
/**
|
package/dist/core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,EACX,MAAM,EAEN,YAAY,EACZ,MAAM,EAEN,GAAG,EAEH,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,EACX,MAAM,EAEN,YAAY,EACZ,MAAM,EAEN,GAAG,EAEH,MAAM,YAAY,CAAA;AAGnB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAK1C,sEAAsE;AACtE,eAAO,MAAM,MAAM,aAAa,CAAA;AAEhC,qEAAqE;AACrE,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,KAAK,CAAA;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAAA;CACzC;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,GAAG;IAChC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAA;CACvB;AASD,4EAA4E;AAC5E,eAAO,MAAM,SAAS,YACZ,MAAM,UACP,MAAM,GAAG,SAAS,KACxB,OACqE,CAAA;AAExE;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAWzD;AAyCD,uEAAuE;AACvE,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACtB;AAED,wEAAwE;AACxE,wBAAsB,QAAQ,CAC7B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,QAAQ,CAAC,CAOnB;AAED,gEAAgE;AAChE,wBAAsB,YAAY,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC,CAIf;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAChC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAaD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC7B,KAAK,EAAE,KAAK,EACZ,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAAC,QAAQ,CAAC,CAQnB"}
|
package/dist/core.js
CHANGED
|
@@ -10,18 +10,20 @@ const M = { as: master };
|
|
|
10
10
|
/** The method `AuthAccount` links a password's email with (D-113). */
|
|
11
11
|
export const method = 'password';
|
|
12
12
|
const invalid = () => new PublicError('INVALID_CREDENTIALS', 'Wrong email or password.');
|
|
13
|
+
/** The current password is known to be the user's: no email to name (D-161). */
|
|
14
|
+
const wrongPassword = () => new PublicError('INVALID_CREDENTIALS', 'Wrong password.');
|
|
13
15
|
/** The attempt a client makes for `subject`; server code is not counted. */
|
|
14
16
|
export const attemptOf = (subject, client) => client === undefined ? undefined : { subject, address: client.address };
|
|
15
17
|
/**
|
|
16
18
|
* Creates the user, its account and its hash in one transaction, as Master:
|
|
17
|
-
* the caller's fields were checked against the list (D-113).
|
|
19
|
+
* the caller's fields were checked against the list (D-113). The email's
|
|
20
|
+
* hook links the account (D-162).
|
|
18
21
|
*/
|
|
19
22
|
export async function createUser(state, email, password, data) {
|
|
20
23
|
const hash = await hashPassword(password);
|
|
21
24
|
const stamp = randomUUID();
|
|
22
25
|
return transaction(async () => {
|
|
23
|
-
const user = await state.users.queries.create({ data }, M);
|
|
24
|
-
await state.auth.linkAccount(user.id, method, email);
|
|
26
|
+
const user = await state.users.queries.create({ data: { ...data, email } }, M);
|
|
25
27
|
await PasswordHash.create({ data: { userId: user.id, hash, stamp } }, M);
|
|
26
28
|
return { user, stamp };
|
|
27
29
|
});
|
|
@@ -73,22 +75,28 @@ export async function verified(state, email, password, attempt) {
|
|
|
73
75
|
export async function checkCurrent(userId, password, attempt) {
|
|
74
76
|
await limited(attempt, async () => {
|
|
75
77
|
if ((await matches(userId, password)) === undefined)
|
|
76
|
-
throw
|
|
78
|
+
throw wrongPassword();
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
/**
|
|
80
|
-
* Replaces `userId`'s hash,
|
|
81
|
-
* pending reset tokens (D-117);
|
|
82
|
+
* Replaces `userId`'s hash, or gives one to an email without (D-162), ends
|
|
83
|
+
* every session of the user and deletes its pending reset tokens (D-117);
|
|
84
|
+
* the new stamp.
|
|
82
85
|
*/
|
|
83
86
|
export async function setPassword(state, userId, password) {
|
|
84
87
|
const stored = await PasswordHash.findUnique({ where: { userId } }, M);
|
|
85
|
-
|
|
88
|
+
const linked = stored !== null ||
|
|
89
|
+
(await state.auth.subjectOf(userId, method)) !== undefined;
|
|
90
|
+
if (!linked) {
|
|
86
91
|
throw new PublicError('NOT_FOUND', `User ${userId} has no password: sign it up with signUp.`);
|
|
87
92
|
}
|
|
88
93
|
const hash = await hashPassword(password);
|
|
89
94
|
const stamp = randomUUID();
|
|
90
95
|
await transaction(async () => {
|
|
91
|
-
|
|
96
|
+
const data = { hash, stamp };
|
|
97
|
+
await (stored === null
|
|
98
|
+
? PasswordHash.create({ data: { userId, ...data } }, M)
|
|
99
|
+
: PasswordHash.update({ where: { userId }, data }, M));
|
|
92
100
|
await PasswordToken.deleteMany({ where: { userId, kind: 'reset' } }, M);
|
|
93
101
|
await state.auth.revokeSessions(userId);
|
|
94
102
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Caller } from 'ultimabase';
|
|
2
|
+
import type { State } from './core.ts';
|
|
3
|
+
type Emailed = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly email: string | null;
|
|
6
|
+
};
|
|
7
|
+
type Draft = {
|
|
8
|
+
email?: string | null | undefined;
|
|
9
|
+
emailVerified?: boolean | undefined;
|
|
10
|
+
};
|
|
11
|
+
/** Only the user itself and Master read the email. */
|
|
12
|
+
export declare const readsEmail: (facts: {
|
|
13
|
+
readonly caller: Caller;
|
|
14
|
+
readonly record: {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
};
|
|
17
|
+
}) => boolean;
|
|
18
|
+
/** Normalizes a created user's email, as at sign-up. */
|
|
19
|
+
export declare function creating(data: Draft): void;
|
|
20
|
+
/** Links the created user's email as its password account. */
|
|
21
|
+
export declare function created(state: State, record: Emailed): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes an updated email, refuses `null` while the account is linked,
|
|
24
|
+
* and unverifies another email unless the write verifies it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function updating(state: State, record: Emailed, data: Draft): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Moves the password account to the new email, in the write's transaction,
|
|
29
|
+
* and deletes the links the old one received.
|
|
30
|
+
*/
|
|
31
|
+
export declare function updated(state: State, record: Emailed, previous: Emailed): Promise<void>;
|
|
32
|
+
/** Tells the old address, once the change commits, when `sendEmail` exists. */
|
|
33
|
+
export declare function committed(state: State, record: Emailed, previous: Emailed): void;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=email-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-field.d.ts","sourceRoot":"","sources":["../src/email-field.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAExC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAQtC,KAAK,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AACrE,KAAK,KAAK,GAAG;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACjC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACnC,CAAA;AAED,sDAAsD;AACtD,eAAO,MAAM,UAAU,UAAW;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CACxC,KAAG,OAKH,CAAA;AAED,wDAAwD;AACxD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,CAI1C;AAED,8DAA8D;AAC9D,wBAAsB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAC7B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,KAAK,GACT,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC5B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CACxB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,OAAO,GACf,IAAI,CAcN"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { isMaster, master, PublicError } from 'ultimabase';
|
|
2
|
+
import { method } from './core.js';
|
|
3
|
+
import { later } from './email-functions.js';
|
|
4
|
+
import { emailOf, valid } from './input.js';
|
|
5
|
+
import { PasswordToken } from './tokens.js';
|
|
6
|
+
const M = { as: master };
|
|
7
|
+
/** Only the user itself and Master read the email. */
|
|
8
|
+
export const readsEmail = (facts) => {
|
|
9
|
+
const { caller, record } = facts;
|
|
10
|
+
return (isMaster(caller) || (caller.kind === 'user' && caller.user.id === record.id));
|
|
11
|
+
};
|
|
12
|
+
/** Normalizes a created user's email, as at sign-up. */
|
|
13
|
+
export function creating(data) {
|
|
14
|
+
if (typeof data.email === 'string') {
|
|
15
|
+
data.email = valid(emailOf, data.email, 'email');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Links the created user's email as its password account. */
|
|
19
|
+
export async function created(state, record) {
|
|
20
|
+
if (record.email === null)
|
|
21
|
+
return;
|
|
22
|
+
await state.auth.linkAccount(record.id, method, record.email);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Normalizes an updated email, refuses `null` while the account is linked,
|
|
26
|
+
* and unverifies another email unless the write verifies it.
|
|
27
|
+
*/
|
|
28
|
+
export async function updating(state, record, data) {
|
|
29
|
+
if (data.email === undefined)
|
|
30
|
+
return;
|
|
31
|
+
const subject = await state.auth.subjectOf(record.id, method);
|
|
32
|
+
if (data.email === null) {
|
|
33
|
+
if (subject === undefined)
|
|
34
|
+
return;
|
|
35
|
+
const message = 'closeAccount removes the email.';
|
|
36
|
+
throw new PublicError('VALIDATION_FAILED', `email: ${message}`, {
|
|
37
|
+
details: { issues: [{ path: ['email'], message }] },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
data.email = valid(emailOf, data.email, 'email');
|
|
41
|
+
if (data.email !== subject)
|
|
42
|
+
data.emailVerified ??= false;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Moves the password account to the new email, in the write's transaction,
|
|
46
|
+
* and deletes the links the old one received.
|
|
47
|
+
*/
|
|
48
|
+
export async function updated(state, record, previous) {
|
|
49
|
+
if (record.email === null || record.email === previous.email)
|
|
50
|
+
return;
|
|
51
|
+
const { auth } = state;
|
|
52
|
+
const subject = await auth.subjectOf(record.id, method);
|
|
53
|
+
// Filling the field from the account changes nothing else.
|
|
54
|
+
if (subject === record.email)
|
|
55
|
+
return;
|
|
56
|
+
if (subject !== undefined)
|
|
57
|
+
await auth.unlinkAccount(record.id, method);
|
|
58
|
+
await auth.linkAccount(record.id, method, record.email);
|
|
59
|
+
const where = { userId: record.id, kind: { in: ['reset', 'verify'] } };
|
|
60
|
+
await PasswordToken.deleteMany({ where }, M);
|
|
61
|
+
}
|
|
62
|
+
/** Tells the old address, once the change commits, when `sendEmail` exists. */
|
|
63
|
+
export function committed(state, record, previous) {
|
|
64
|
+
const { sendEmail } = state;
|
|
65
|
+
const [from, to] = [previous.email, record.email];
|
|
66
|
+
if (sendEmail === undefined || from === null || to === null || from === to) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
later(state, async () => {
|
|
70
|
+
await sendEmail({
|
|
71
|
+
kind: 'emailChanged',
|
|
72
|
+
to: from,
|
|
73
|
+
newEmail: to,
|
|
74
|
+
userId: record.id,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-functions.d.ts","sourceRoot":"","sources":["../src/email-functions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"email-functions.d.ts","sourceRoot":"","sources":["../src/email-functions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAIN,WAAW,EAEX,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAWtC,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGnD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAUnE;AAED,6CAA6C;AAC7C,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEvD;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,CACpB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,KACN,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAA;AAErC,eAAO,MAAM,QAAQ,cACR,SAAS,KAAG,MAOvB,CAAA;AAWF,4EAA4E;AAC5E,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,KAAK,EACpB,IAAI,EAAE,MAAM,GACV,SAAS,aAAa,EAAE,CAqE1B"}
|
package/dist/email-functions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { allow, defineFunction, master, PublicError } from 'ultimabase';
|
|
2
|
-
import { method, setPassword } from './core.js';
|
|
3
|
-
import { outputSchema, requestResetInput, resetPasswordInput, verifyEmailInput, } from './input.js';
|
|
1
|
+
import { allow, defineFunction, master, PublicError, transaction, } from 'ultimabase';
|
|
2
|
+
import { attemptOf, checkCurrent, method, setPassword } from './core.js';
|
|
3
|
+
import { confirmEmailChangeInput, outputSchema, requestEmailChangeInput, requestResetInput, resetPasswordInput, verifyEmailInput, } from './input.js';
|
|
4
4
|
import { requestLimited } from './models.js';
|
|
5
5
|
import { emailLimited, issueToken, redeemToken } from './tokens.js';
|
|
6
6
|
/**
|
|
@@ -22,7 +22,7 @@ export const mailerOf = (sendEmail) => async (kind, userId, to) => {
|
|
|
22
22
|
const refused = await emailLimited(userId);
|
|
23
23
|
if (refused !== undefined)
|
|
24
24
|
return refused;
|
|
25
|
-
const token = await issueToken(userId, kind);
|
|
25
|
+
const token = await issueToken(userId, kind, to);
|
|
26
26
|
await sendEmail({ kind, to, token, userId });
|
|
27
27
|
return undefined;
|
|
28
28
|
};
|
|
@@ -60,7 +60,7 @@ export function emailFunctions(stateOf, mail) {
|
|
|
60
60
|
output: done,
|
|
61
61
|
run: async ({ input: { input } }) => {
|
|
62
62
|
const state = stateOf();
|
|
63
|
-
const userId = await redeemToken(input.token, 'reset');
|
|
63
|
+
const { userId } = await redeemToken(input.token, 'reset');
|
|
64
64
|
await setPassword(state, userId, input.password);
|
|
65
65
|
await verify(state, userId);
|
|
66
66
|
return true;
|
|
@@ -71,7 +71,8 @@ export function emailFunctions(stateOf, mail) {
|
|
|
71
71
|
input: verifyEmailInput,
|
|
72
72
|
output: done,
|
|
73
73
|
run: async ({ input: { input } }) => {
|
|
74
|
-
|
|
74
|
+
const { userId } = await redeemToken(input.token, 'verify');
|
|
75
|
+
await verify(stateOf(), userId);
|
|
75
76
|
return true;
|
|
76
77
|
},
|
|
77
78
|
}).with({ access: allow.public });
|
|
@@ -103,5 +104,46 @@ export function emailFunctions(stateOf, mail) {
|
|
|
103
104
|
resetPassword,
|
|
104
105
|
verifyEmail,
|
|
105
106
|
sendVerificationEmail,
|
|
107
|
+
...emailChangeFunctions(stateOf, mail),
|
|
106
108
|
];
|
|
107
109
|
}
|
|
110
|
+
/** `requestEmailChange` and `confirmEmailChange` (D-162). */
|
|
111
|
+
function emailChangeFunctions(stateOf, mail) {
|
|
112
|
+
const requestEmailChange = defineFunction({
|
|
113
|
+
name: 'requestEmailChange',
|
|
114
|
+
input: requestEmailChangeInput,
|
|
115
|
+
output: done,
|
|
116
|
+
run: async ({ input: { input }, caller, client }) => {
|
|
117
|
+
if (caller.kind !== 'user') {
|
|
118
|
+
throw new PublicError('UNAUTHENTICATED', 'requestEmailChange needs a signed-in user.');
|
|
119
|
+
}
|
|
120
|
+
const { id } = caller.user;
|
|
121
|
+
await checkCurrent(id, input.currentPassword, attemptOf(id, client));
|
|
122
|
+
const { newEmail } = input;
|
|
123
|
+
// The caller proved the password: telling it is no enumeration.
|
|
124
|
+
if ((await stateOf().auth.accountOf(method, newEmail)) !== undefined) {
|
|
125
|
+
throw new PublicError('UNIQUE_VIOLATION', 'This email has an account.');
|
|
126
|
+
}
|
|
127
|
+
const refused = await mail('emailChange', id, newEmail);
|
|
128
|
+
if (refused !== undefined)
|
|
129
|
+
throw refused;
|
|
130
|
+
return true;
|
|
131
|
+
},
|
|
132
|
+
}).with({ access: allow.public });
|
|
133
|
+
const confirmEmailChange = defineFunction({
|
|
134
|
+
name: 'confirmEmailChange',
|
|
135
|
+
input: confirmEmailChangeInput,
|
|
136
|
+
output: done,
|
|
137
|
+
run: async ({ input: { input } }) => {
|
|
138
|
+
const { users } = stateOf();
|
|
139
|
+
// A taken email undoes the redeem too: the link works until it expires.
|
|
140
|
+
await transaction(async () => {
|
|
141
|
+
const { userId, email } = await redeemToken(input.token, 'emailChange');
|
|
142
|
+
const data = { email, emailVerified: true };
|
|
143
|
+
await users.queries.update({ where: { id: userId }, data }, M);
|
|
144
|
+
});
|
|
145
|
+
return true;
|
|
146
|
+
},
|
|
147
|
+
}).with({ access: allow.public });
|
|
148
|
+
return [requestEmailChange, confirmEmailChange];
|
|
149
|
+
}
|
package/dist/input.d.ts
CHANGED
|
@@ -96,6 +96,18 @@ export declare const verifyEmailInput: StandardSchema<Named<{
|
|
|
96
96
|
}>, Named<{
|
|
97
97
|
readonly token: string;
|
|
98
98
|
}>>;
|
|
99
|
+
type RequestEmailChange = {
|
|
100
|
+
readonly newEmail: string;
|
|
101
|
+
readonly currentPassword: string;
|
|
102
|
+
};
|
|
103
|
+
/** What `requestEmailChange` takes: the new email and the password (D-162). */
|
|
104
|
+
export declare const requestEmailChangeInput: StandardSchema<Named<RequestEmailChange>, Named<RequestEmailChange>>;
|
|
105
|
+
/** What `confirmEmailChange` takes: the email's token (D-162). */
|
|
106
|
+
export declare const confirmEmailChangeInput: StandardSchema<Named<{
|
|
107
|
+
readonly token: string;
|
|
108
|
+
}>, Named<{
|
|
109
|
+
readonly token: string;
|
|
110
|
+
}>>;
|
|
99
111
|
/** The email, the password and the listed user fields, by their names. */
|
|
100
112
|
export declare const signUpInput: (fields: ReadonlyMap<string, Property>) => StandardSchema<Named<SignUp>, Named<SignUp>>;
|
|
101
113
|
/** An output's schema: typed and exported, never validated (D-45). */
|
package/dist/input.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"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"}
|
package/dist/input.js
CHANGED
|
@@ -210,6 +210,13 @@ export const resetPasswordInput = namedInput('ResetPassword', new Map([
|
|
|
210
210
|
]));
|
|
211
211
|
/** What `verifyEmail` takes: the email's token (D-117). */
|
|
212
212
|
export const verifyEmailInput = namedInput('VerifyEmail', new Map([['token', property(text, string)]]));
|
|
213
|
+
/** What `requestEmailChange` takes: the new email and the password (D-162). */
|
|
214
|
+
export const requestEmailChangeInput = namedInput('RequestEmailChange', new Map([
|
|
215
|
+
['newEmail', email],
|
|
216
|
+
['currentPassword', property(text, string)],
|
|
217
|
+
]));
|
|
218
|
+
/** What `confirmEmailChange` takes: the email's token (D-162). */
|
|
219
|
+
export const confirmEmailChangeInput = namedInput('ConfirmEmailChange', new Map([['token', property(text, string)]]));
|
|
213
220
|
/** The email, the password and the listed user fields, by their names. */
|
|
214
221
|
export const signUpInput = (fields) => namedInput('SignUp', new Map([
|
|
215
222
|
['email', email],
|
package/dist/models.d.ts
CHANGED
|
@@ -31,16 +31,20 @@ export declare const PasswordRequest: ModelOf<'PasswordRequest', RequestFields,
|
|
|
31
31
|
* (D-114).
|
|
32
32
|
*/
|
|
33
33
|
export declare function networkOf(address: string): string;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Where an attempt comes from: a client's address, none for server code
|
|
36
|
+
* that is still counted (D-161), `undefined` for server code that is not.
|
|
37
|
+
*/
|
|
35
38
|
export type Attempt = {
|
|
36
39
|
readonly subject: string;
|
|
37
|
-
readonly address
|
|
40
|
+
readonly address?: string;
|
|
38
41
|
} | undefined;
|
|
39
42
|
/**
|
|
40
43
|
* Counts an attempt as failed until `run` succeeds, so concurrent attempts
|
|
41
44
|
* cannot pass the limit (D-113, D-114). Within 15 minutes, the 11th from one
|
|
42
|
-
* address on a subject is refused, as the 101st on it or from the address
|
|
43
|
-
*
|
|
45
|
+
* address on a subject is refused, as the 101st on it or from the address,
|
|
46
|
+
* and the 11th without an address on a subject (D-161); a success clears
|
|
47
|
+
* its address's count, a busy refusal is not counted.
|
|
44
48
|
*/
|
|
45
49
|
export declare function limited<T>(attempt: Attempt, run: () => Promise<T>): Promise<T>;
|
|
46
50
|
/** What the hourly limits count (D-114, D-117). */
|
package/dist/models.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAuB,WAAW,EAAE,MAAM,YAAY,CAAA;AAO7D,eAAO,MAAM,IAAI,EAAE,MAAuB,CAAA;AAE1C,KAAK,UAAU,GAAG;IACjB,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;IACzD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACnD,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;CACxD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,cAAc,EAAE,UAAU,EAAE,EAAE,CAQ9D,CAAA;AAEH,KAAK,aAAa,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;CAC7C,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,iBAAiB,EAAE,aAAa,EAAE,EAAE,CASvE,CAAA;AAEH,KAAK,aAAa,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACnD,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;CAC7C,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,iBAAiB,EAAE,aAAa,EAAE,EAAE,CASvE,CAAA;AAEH;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMjD;AAYD
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAuB,WAAW,EAAE,MAAM,YAAY,CAAA;AAO7D,eAAO,MAAM,IAAI,EAAE,MAAuB,CAAA;AAE1C,KAAK,UAAU,GAAG;IACjB,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;IACzD,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACnD,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;CACxD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,cAAc,EAAE,UAAU,EAAE,EAAE,CAQ9D,CAAA;AAEH,KAAK,aAAa,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;CAC7C,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,iBAAiB,EAAE,aAAa,EAAE,EAAE,CASvE,CAAA;AAEH,KAAK,aAAa,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACnD,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAA;CAC7C,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,iBAAiB,EAAE,aAAa,EAAE,EAAE,CASvE,CAAA;AAEH;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMjD;AAYD;;;GAGG;AACH,MAAM,MAAM,OAAO,GAClB;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAA;AAoCpE;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC9B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CA+BZ;AAED,mDAAmD;AACnD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEjE;;;;GAIG;AACH,wBAAsB,MAAM,CAC3B,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAmBlC;AAED,4FAA4F;AAC5F,wBAAsB,cAAc,CACnC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
package/dist/models.js
CHANGED
|
@@ -53,32 +53,46 @@ const tooMany = (oldest, span, now) => new PublicError('TOO_MANY_ATTEMPTS', 'Too
|
|
|
53
53
|
retryAfter: Math.max(Math.ceil((oldest.getTime() + span - now) / 1000), 1),
|
|
54
54
|
},
|
|
55
55
|
});
|
|
56
|
+
/** What an attempt without an address is stored with; no address is it. */
|
|
57
|
+
const server = 'server';
|
|
56
58
|
/** The `n`th most recent failure but `id` matching `where`, if there is one. */
|
|
57
59
|
async function nth(where, id, n) {
|
|
58
60
|
const failures = await PasswordAttempt.findMany({ where: { ...where, id: { not: id } }, orderBy: { at: 'desc' }, take: n }, M);
|
|
59
61
|
return failures[n - 1]?.at;
|
|
60
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The failures but `id` that refuse the attempt: from one address, the 10th
|
|
65
|
+
* on the subject or the 100th on it or from the address; without one, the
|
|
66
|
+
* 10th on the subject from anywhere, and no address is filled (D-161).
|
|
67
|
+
*/
|
|
68
|
+
async function reached(subject, address, id) {
|
|
69
|
+
if (address === undefined)
|
|
70
|
+
return [await nth({ subject }, id, limit)];
|
|
71
|
+
return [
|
|
72
|
+
await nth({ subject, address }, id, limit),
|
|
73
|
+
await nth({ subject }, id, ceiling),
|
|
74
|
+
await nth({ address }, id, ceiling),
|
|
75
|
+
];
|
|
76
|
+
}
|
|
61
77
|
/**
|
|
62
78
|
* Counts an attempt as failed until `run` succeeds, so concurrent attempts
|
|
63
79
|
* cannot pass the limit (D-113, D-114). Within 15 minutes, the 11th from one
|
|
64
|
-
* address on a subject is refused, as the 101st on it or from the address
|
|
65
|
-
*
|
|
80
|
+
* address on a subject is refused, as the 101st on it or from the address,
|
|
81
|
+
* and the 11th without an address on a subject (D-161); a success clears
|
|
82
|
+
* its address's count, a busy refusal is not counted.
|
|
66
83
|
*/
|
|
67
84
|
export async function limited(attempt, run) {
|
|
68
85
|
if (attempt === undefined)
|
|
69
86
|
return run();
|
|
70
87
|
const { subject } = attempt;
|
|
71
|
-
const
|
|
88
|
+
const network = attempt.address === undefined ? undefined : networkOf(attempt.address);
|
|
89
|
+
const address = network ?? server;
|
|
72
90
|
const now = Date.now();
|
|
73
91
|
const since = new Date(now - window);
|
|
74
92
|
await PasswordAttempt.deleteMany({ where: { at: { lte: since } } }, M);
|
|
75
93
|
const { id } = await PasswordAttempt.create({ data: { subject, address, at: new Date(now) } }, M);
|
|
76
94
|
// Several may be reached: the latest says when to retry.
|
|
77
|
-
const [oldest] =
|
|
78
|
-
await nth({ subject, address }, id, limit),
|
|
79
|
-
await nth({ subject }, id, ceiling),
|
|
80
|
-
await nth({ address }, id, ceiling),
|
|
81
|
-
]
|
|
95
|
+
const [oldest] = (await reached(subject, network, id))
|
|
82
96
|
.filter((at) => at !== undefined)
|
|
83
97
|
.toSorted((a, b) => b.getTime() - a.getTime());
|
|
84
98
|
if (oldest !== undefined) {
|
package/dist/plugin.d.ts
CHANGED
|
@@ -45,6 +45,15 @@ export interface PasswordCommands<F> {
|
|
|
45
45
|
readonly id: string;
|
|
46
46
|
readonly password: string;
|
|
47
47
|
}, options?: Options): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Checks the user's current password before a sensitive action, as
|
|
50
|
+
* Master or as the user; opens no session. Always counted: with a
|
|
51
|
+
* `client` as changePassword, otherwise 10 failures per user (D-161).
|
|
52
|
+
*/
|
|
53
|
+
checkPassword(args: {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly password: string;
|
|
56
|
+
}, options?: SignInOptions): Promise<void>;
|
|
48
57
|
}
|
|
49
58
|
/** password's types (D-57). */
|
|
50
59
|
export interface Password extends PluginTypes {
|
|
@@ -55,6 +64,11 @@ export interface Password extends PluginTypes {
|
|
|
55
64
|
readonly emailVerified: Field<boolean, {
|
|
56
65
|
readonly default: false;
|
|
57
66
|
}>;
|
|
67
|
+
/** The password account's email (D-162). */
|
|
68
|
+
readonly email: Field<string | null, {
|
|
69
|
+
readonly optional: true;
|
|
70
|
+
readonly unique: true;
|
|
71
|
+
}>;
|
|
58
72
|
};
|
|
59
73
|
readonly commands: PasswordCommands<this['fields']>;
|
|
60
74
|
}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,kBAAkB,CAAA;AAErD,OAAO,KAAK,EACX,MAAM,EAEN,KAAK,EACL,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,WAAW,EACX,OAAO,EACP,QAAQ,EACR,KAAK,EACL,MAAM,EACN,MAAM,YAAY,CAAA;AASnB,OAAO,KAAK,EAAE,QAAQ,EAAS,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|