@rexezuge/identity 1.0.0 → 1.0.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.
- package/README.md +61 -0
- package/dist/ResolvedAccount.d.ts +1 -1
- package/dist/ResolvedAccount.d.ts.map +1 -1
- package/dist/ResolvedAccount.js.map +1 -1
- package/dist/UserDAO.d.ts +4 -4
- package/dist/UserDAO.d.ts.map +1 -1
- package/dist/UserDAO.js +23 -17
- package/dist/UserDAO.js.map +1 -1
- package/dist/UserEmailDAO.d.ts +2 -2
- package/dist/UserEmailDAO.d.ts.map +1 -1
- package/dist/UserEmailDAO.js +8 -20
- package/dist/UserEmailDAO.js.map +1 -1
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @rexezuge/identity
|
|
2
|
+
|
|
3
|
+
Account layer with address resolution, user DAOs, and canonical identity migrations.
|
|
4
|
+
|
|
5
|
+
Depends on `@rexezuge/d1` (persistence + statement splitter), `@rexezuge/errors`, and `@rexezuge/shared` (email normalization).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @rexezuge/identity
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. The invariant: the address is not the account
|
|
14
|
+
|
|
15
|
+
A login address can move between accounts, so resolution goes through the verified `user_emails` registry — never the mutable address string — and a revoked row resolves to nothing. The only safe fall-through is a database whose identity migration has not run yet (`isMissingSchemaError`); every other D1 failure rethrows, because a transient error answered with the wrong account is a full cross-account read.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { resolveAccount, type AccountLookupDeps } from '@rexezuge/identity';
|
|
19
|
+
|
|
20
|
+
const account = await resolveAccount(deps, 'user@example.com'); // ResolvedAccount | null
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`UserIdentityService` is the stateful service over the same rules (register, verify, resolve); the free functions exist so account bootstrap and the service share one implementation.
|
|
24
|
+
|
|
25
|
+
## 2. DAOs
|
|
26
|
+
|
|
27
|
+
| Export | Role |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `UserDAO` | `users` table: frozen anchor id, lookup by id/anchor/email |
|
|
30
|
+
| `UserEmailDAO` | `user_emails` registry: claims, verification, revocation |
|
|
31
|
+
| `newAccountId` / `newAnchor` | ID generation for new accounts |
|
|
32
|
+
| `summarize` / `AccountSummary` | safe-to-log account projection |
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { UserDAO, UserEmailDAO } from '@rexezuge/identity';
|
|
36
|
+
|
|
37
|
+
const users = new UserDAO(env.DB);
|
|
38
|
+
const emails = new UserEmailDAO(env.DB);
|
|
39
|
+
await users.getById(account.id);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 3. Migrations
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { applyIdentityMigrations } from '@rexezuge/identity';
|
|
46
|
+
|
|
47
|
+
await applyIdentityMigrations(env.DB); // fresh install: users.sql
|
|
48
|
+
await applyIdentityMigrations(env.DB, { upgrade: true }); // legacy DB: + upgrade-user-identity.sql (rerunnable)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Executed through `@rexezuge/d1`'s splitter — one `batch()` per file, matching D1's own per-file apply, so a failure names its file. `IDENTITY_MIGRATIONS` / `IDENTITY_MIGRATION_SQL` carry the same pair as data for repos that apply migrations through their own runner. The upgrade backfills a verified registry row for every anchor, which is what lets pre-identity databases resolve addresses the old way until they migrate.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Verifying this package
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm --filter @rexezuge/identity exec tsc -p tsconfig.json
|
|
59
|
+
pnpm --filter @rexezuge/identity typecheck
|
|
60
|
+
pnpm --filter @rexezuge/identity test
|
|
61
|
+
```
|
|
@@ -44,7 +44,7 @@ export interface ResolvedAccount {
|
|
|
44
44
|
/** The denormalized subset routes and the permission path use. */
|
|
45
45
|
export type AccountSummary = Pick<ResolvedAccount, 'id' | 'email' | 'username'>;
|
|
46
46
|
/** Shape every `users` read has, whether or not the identity migration has run. */
|
|
47
|
-
export type UserRowLike = Pick<UserRow, 'id' | 'email' | 'current_email' | 'username'> |
|
|
47
|
+
export type UserRowLike = Pick<UserRow, 'id' | 'email' | 'current_email' | 'username'> | undefined;
|
|
48
48
|
/** Project a `users` row onto a `ResolvedAccount`. */
|
|
49
49
|
export declare function summarize(row: UserRowLike): ResolvedAccount | null;
|
|
50
50
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResolvedAccount.d.ts","sourceRoot":"","sources":["../src/ResolvedAccount.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC;AAEhF,mFAAmF;AACnF,MAAM,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"ResolvedAccount.d.ts","sourceRoot":"","sources":["../src/ResolvedAccount.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC;AAEhF,mFAAmF;AACnF,MAAM,MAAM,WAAW,GACnB,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,OAAO,GAAG,eAAe,GAAG,UAAU,CAAC,GAC5D,SAAS,CAAC;AAEd,sDAAsD;AACtD,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,CAIlE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResolvedAccount.js","sourceRoot":"","sources":["../src/ResolvedAccount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ResolvedAccount.js","sourceRoot":"","sources":["../src/ResolvedAccount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuDzC,sDAAsD;AACtD,MAAM,UAAU,SAAS,CAAC,GAAgB;IACxC,OAAO,GAAG,EAAE,EAAE;QACZ,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI,EAAE;QAC/H,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;AACnD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,gBAAgB,CAAC;AACpE,CAAC"}
|
package/dist/UserDAO.d.ts
CHANGED
|
@@ -66,11 +66,11 @@ export declare class UserDAO extends BaseDAO {
|
|
|
66
66
|
* findable here by its new one. It is the pre-identity floor and the
|
|
67
67
|
* attribution read.
|
|
68
68
|
*/
|
|
69
|
-
getByEmail(email: string): Promise<UserRow |
|
|
69
|
+
getByEmail(email: string): Promise<UserRow | undefined>;
|
|
70
70
|
/** The account behind a stable key. Null on a pre-identity database. */
|
|
71
|
-
getById(id: string): Promise<UserRow |
|
|
71
|
+
getById(id: string): Promise<UserRow | undefined>;
|
|
72
72
|
/** The account currently signing in with this address. Identity-era only. */
|
|
73
|
-
getByCurrentEmail(email: string): Promise<UserRow |
|
|
73
|
+
getByCurrentEmail(email: string): Promise<UserRow | undefined>;
|
|
74
74
|
/**
|
|
75
75
|
* Move an account's sign-in address.
|
|
76
76
|
*
|
|
@@ -81,7 +81,7 @@ export declare class UserDAO extends BaseDAO {
|
|
|
81
81
|
*/
|
|
82
82
|
setCurrentEmail(id: string, email: string, now: number): Promise<void>;
|
|
83
83
|
/** Lookup by handle. */
|
|
84
|
-
getByUsernameCi(usernameCi: string): Promise<UserRow |
|
|
84
|
+
getByUsernameCi(usernameCi: string): Promise<UserRow | undefined>;
|
|
85
85
|
}
|
|
86
86
|
export { normalizeUsername };
|
|
87
87
|
export type { UserRow };
|
package/dist/UserDAO.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserDAO.d.ts","sourceRoot":"","sources":["../src/UserDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,iBAAS,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;;;GASG;AACH,qBAAa,OAAQ,SAAQ,OAAO;gBACf,QAAQ,EAAE,WAAW;IAIxC;;;;;;;;;;;;;;;;;;;OAmBG;IACU,UAAU,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"UserDAO.d.ts","sourceRoot":"","sources":["../src/UserDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,iBAAS,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;;;GASG;AACH,qBAAa,OAAQ,SAAQ,OAAO;gBACf,QAAQ,EAAE,WAAW;IAIxC;;;;;;;;;;;;;;;;;;;OAmBG;IACU,UAAU,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAc3G;;;;;OAKG;IACU,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5F,qEAAqE;IACxD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASzF;;;;;;OAMG;IACU,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAIpE,wEAAwE;IAC3D,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAI9D,6EAA6E;IAChE,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAI3E;;;;;;;OAOG;IACU,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInF,wBAAwB;IACX,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;CAG/E;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/UserDAO.js
CHANGED
|
@@ -52,10 +52,12 @@ export class UserDAO extends BaseDAO {
|
|
|
52
52
|
const anchor = input.anchor.toLowerCase();
|
|
53
53
|
const loginEmail = input.loginEmail.toLowerCase();
|
|
54
54
|
const id = newAccountId();
|
|
55
|
-
await this.run(()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
await this.run('INSERT INTO users (email, created_at, id, current_email) VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING', [
|
|
56
|
+
anchor,
|
|
57
|
+
input.now,
|
|
58
|
+
id,
|
|
59
|
+
loginEmail,
|
|
60
|
+
]);
|
|
59
61
|
const row = await this.getByEmail(anchor);
|
|
60
62
|
return row?.id === id ? id : null;
|
|
61
63
|
}
|
|
@@ -66,17 +68,21 @@ export class UserDAO extends BaseDAO {
|
|
|
66
68
|
* `UserIdentityService` instead.
|
|
67
69
|
*/
|
|
68
70
|
async ensureUsername(idOrEmail, username, now) {
|
|
69
|
-
await this.run(()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
await this.run('UPDATE users SET username = COALESCE(username, ?), updated_at = COALESCE(updated_at, ?) WHERE id = ? OR email = ?', [
|
|
72
|
+
normalizeUsername(username),
|
|
73
|
+
now,
|
|
74
|
+
idOrEmail,
|
|
75
|
+
idOrEmail,
|
|
76
|
+
]);
|
|
73
77
|
}
|
|
74
78
|
/** @param idOrEmail The stable account key, or an anchor address. */
|
|
75
79
|
async setUsername(idOrEmail, username, now) {
|
|
76
|
-
await this.run(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
await this.run('UPDATE users SET username = ?, updated_at = ? WHERE id = ? OR email = ?', [
|
|
81
|
+
normalizeUsername(username),
|
|
82
|
+
now,
|
|
83
|
+
idOrEmail,
|
|
84
|
+
idOrEmail,
|
|
85
|
+
]);
|
|
80
86
|
}
|
|
81
87
|
/**
|
|
82
88
|
* Anchor lookup — used for legacy `*_email` values and cascade resolution.
|
|
@@ -86,15 +92,15 @@ export class UserDAO extends BaseDAO {
|
|
|
86
92
|
* attribution read.
|
|
87
93
|
*/
|
|
88
94
|
async getByEmail(email) {
|
|
89
|
-
return this.first(
|
|
95
|
+
return this.first('SELECT * FROM users WHERE email = ? LIMIT 1', [email.toLowerCase()]);
|
|
90
96
|
}
|
|
91
97
|
/** The account behind a stable key. Null on a pre-identity database. */
|
|
92
98
|
async getById(id) {
|
|
93
|
-
return this.first(
|
|
99
|
+
return this.first('SELECT * FROM users WHERE id = ? LIMIT 1', [id]);
|
|
94
100
|
}
|
|
95
101
|
/** The account currently signing in with this address. Identity-era only. */
|
|
96
102
|
async getByCurrentEmail(email) {
|
|
97
|
-
return this.first(
|
|
103
|
+
return this.first('SELECT * FROM users WHERE current_email = ? LIMIT 1', [email.toLowerCase()]);
|
|
98
104
|
}
|
|
99
105
|
/**
|
|
100
106
|
* Move an account's sign-in address.
|
|
@@ -105,11 +111,11 @@ export class UserDAO extends BaseDAO {
|
|
|
105
111
|
* failure between steps never leaves the account without a way to sign in.
|
|
106
112
|
*/
|
|
107
113
|
async setCurrentEmail(id, email, now) {
|
|
108
|
-
await this.run(
|
|
114
|
+
await this.run('UPDATE users SET current_email = ?, updated_at = ? WHERE id = ?', [email.toLowerCase(), now, id]);
|
|
109
115
|
}
|
|
110
116
|
/** Lookup by handle. */
|
|
111
117
|
async getByUsernameCi(usernameCi) {
|
|
112
|
-
return this.first(
|
|
118
|
+
return this.first('SELECT * FROM users WHERE username = ? LIMIT 1', [usernameCi.toLowerCase()]);
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
export { normalizeUsername };
|
package/dist/UserDAO.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserDAO.js","sourceRoot":"","sources":["../src/UserDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;;;;;;;;;GAUG;AACH,SAAS,iBAAiB,CAAC,QAAgB;IACzC,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,OAAQ,SAAQ,OAAO;IAClC,YAAmB,QAAqB;QACtC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClB,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACI,KAAK,CAAC,UAAU,CAAC,KAA0D;QAChF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1B,MAAM,IAAI,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"UserDAO.js","sourceRoot":"","sources":["../src/UserDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;;;;;;;;;GAUG;AACH,SAAS,iBAAiB,CAAC,QAAgB;IACzC,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,OAAQ,SAAQ,OAAO;IAClC,YAAmB,QAAqB;QACtC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClB,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACI,KAAK,CAAC,UAAU,CAAC,KAA0D;QAChF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1B,MAAM,IAAI,CAAC,GAAG,CAAC,qGAAqG,EAAE;YACpH,MAAM;YACN,KAAK,CAAC,GAAG;YACT,EAAE;YACF,UAAU;SACX,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1C,OAAO,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpC,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,cAAc,CAAC,SAAiB,EAAE,QAAgB,EAAE,GAAW;QAC1E,MAAM,IAAI,CAAC,GAAG,CAAC,mHAAmH,EAAE;YAClI,iBAAiB,CAAC,QAAQ,CAAC;YAC3B,GAAG;YACH,SAAS;YACT,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IAED,qEAAqE;IAC9D,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,QAAgB,EAAE,GAAW;QACvE,MAAM,IAAI,CAAC,GAAG,CAAC,yEAAyE,EAAE;YACxF,iBAAiB,CAAC,QAAQ,CAAC;YAC3B,GAAG;YACH,SAAS;YACT,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,UAAU,CAAC,KAAa;QACnC,OAAO,IAAI,CAAC,KAAK,CAAU,6CAA6C,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACnG,CAAC;IAED,wEAAwE;IACjE,KAAK,CAAC,OAAO,CAAC,EAAU;QAC7B,OAAO,IAAI,CAAC,KAAK,CAAU,0CAA0C,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC;IAED,6EAA6E;IACtE,KAAK,CAAC,iBAAiB,CAAC,KAAa;QAC1C,OAAO,IAAI,CAAC,KAAK,CAAU,qDAAqD,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC3G,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,eAAe,CAAC,EAAU,EAAE,KAAa,EAAE,GAAW;QACjE,MAAM,IAAI,CAAC,GAAG,CAAC,iEAAiE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IACpH,CAAC;IAED,wBAAwB;IACjB,KAAK,CAAC,eAAe,CAAC,UAAkB;QAC7C,OAAO,IAAI,CAAC,KAAK,CAAU,gDAAgD,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC3G,CAAC;CACF;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
package/dist/UserEmailDAO.d.ts
CHANGED
|
@@ -35,9 +35,9 @@ export declare class UserEmailDAO extends BaseDAO {
|
|
|
35
35
|
isVerified: boolean;
|
|
36
36
|
now: number;
|
|
37
37
|
}): Promise<EmailClaim>;
|
|
38
|
-
get(email: string): Promise<UserEmailRow |
|
|
38
|
+
get(email: string): Promise<UserEmailRow | undefined>;
|
|
39
39
|
/** Login resolution: only a verified address identifies an account. */
|
|
40
|
-
resolveVerified(email: string): Promise<UserEmailRow |
|
|
40
|
+
resolveVerified(email: string): Promise<UserEmailRow | undefined>;
|
|
41
41
|
listByUserId(userId: string): Promise<UserEmailRow[]>;
|
|
42
42
|
/** Revoke an address for login while keeping it resolvable for attribution. */
|
|
43
43
|
revoke(email: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserEmailDAO.d.ts","sourceRoot":"","sources":["../src/UserEmailDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAEvD,qBAAa,YAAa,SAAQ,OAAO;gBACpB,QAAQ,EAAE,WAAW;IAIxC;;;;;;;OAOG;IACU,QAAQ,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"UserEmailDAO.d.ts","sourceRoot":"","sources":["../src/UserEmailDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAEvD,qBAAa,YAAa,SAAQ,OAAO;gBACpB,QAAQ,EAAE,WAAW;IAIxC;;;;;;;OAOG;IACU,QAAQ,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAazG,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAOlE,uEAAuE;IAC1D,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAIjE,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAIlE,+EAA+E;IAClE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD;;;OAGG;IACU,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGnF"}
|
package/dist/UserEmailDAO.js
CHANGED
|
@@ -16,46 +16,34 @@ export class UserEmailDAO extends BaseDAO {
|
|
|
16
16
|
const existing = await this.get(email);
|
|
17
17
|
if (existing && existing.is_verified === 1)
|
|
18
18
|
return 'already-claimed';
|
|
19
|
-
await this.run((
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ON CONFLICT(email) DO UPDATE SET user_id = excluded.user_id, is_verified = excluded.is_verified`)
|
|
23
|
-
.bind(email, input.userId, input.isVerified ? 1 : 0, input.now)
|
|
24
|
-
.run(), 'register user email');
|
|
19
|
+
await this.run(`INSERT INTO user_emails (email, user_id, is_verified, created_at)
|
|
20
|
+
VALUES (?, ?, ?, ?)
|
|
21
|
+
ON CONFLICT(email) DO UPDATE SET user_id = excluded.user_id, is_verified = excluded.is_verified`, [email, input.userId, input.isVerified ? 1 : 0, input.now]);
|
|
25
22
|
return 'claimed';
|
|
26
23
|
}
|
|
27
24
|
async get(email) {
|
|
28
25
|
// Lowercase the *parameter*: the column is stored lowercased by every
|
|
29
26
|
// writer, so `lower(email) = ?` matched identically while making the
|
|
30
27
|
// primary key unusable.
|
|
31
|
-
return this.first(
|
|
28
|
+
return this.first('SELECT * FROM user_emails WHERE email = ? LIMIT 1', [email.toLowerCase()]);
|
|
32
29
|
}
|
|
33
30
|
/** Login resolution: only a verified address identifies an account. */
|
|
34
31
|
async resolveVerified(email) {
|
|
35
|
-
return this.first(
|
|
36
|
-
.prepare('SELECT * FROM user_emails WHERE email = ? AND is_verified = 1 LIMIT 1')
|
|
37
|
-
.bind(email.toLowerCase())
|
|
38
|
-
.first(), 'resolve verified user email');
|
|
32
|
+
return this.first('SELECT * FROM user_emails WHERE email = ? AND is_verified = 1 LIMIT 1', [email.toLowerCase()]);
|
|
39
33
|
}
|
|
40
34
|
async listByUserId(userId) {
|
|
41
|
-
return this.all(
|
|
42
|
-
.prepare('SELECT * FROM user_emails WHERE user_id = ? ORDER BY is_verified DESC, created_at ASC')
|
|
43
|
-
.bind(userId)
|
|
44
|
-
.all(), 'list user emails by user id');
|
|
35
|
+
return this.all('SELECT * FROM user_emails WHERE user_id = ? ORDER BY is_verified DESC, created_at ASC', [userId]);
|
|
45
36
|
}
|
|
46
37
|
/** Revoke an address for login while keeping it resolvable for attribution. */
|
|
47
38
|
async revoke(email) {
|
|
48
|
-
await this.run(
|
|
39
|
+
await this.run('UPDATE user_emails SET is_verified = 0 WHERE email = ?', [email.toLowerCase()]);
|
|
49
40
|
}
|
|
50
41
|
/**
|
|
51
42
|
* Revoke every verified address for an account. Used when an account's login
|
|
52
43
|
* address changes, so only the new address can authenticate it.
|
|
53
44
|
*/
|
|
54
45
|
async revokeAllVerified(userId, exceptEmail) {
|
|
55
|
-
await this.run(
|
|
56
|
-
.prepare('UPDATE user_emails SET is_verified = 0 WHERE user_id = ? AND email != ?')
|
|
57
|
-
.bind(userId, exceptEmail.toLowerCase())
|
|
58
|
-
.run(), 'revoke verified user emails');
|
|
46
|
+
await this.run('UPDATE user_emails SET is_verified = 0 WHERE user_id = ? AND email != ?', [userId, exceptEmail.toLowerCase()]);
|
|
59
47
|
}
|
|
60
48
|
}
|
|
61
49
|
//# sourceMappingURL=UserEmailDAO.js.map
|
package/dist/UserEmailDAO.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserEmailDAO.js","sourceRoot":"","sources":["../src/UserEmailDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAwBvC,MAAM,OAAO,YAAa,SAAQ,OAAO;IACvC,YAAmB,QAAqB;QACtC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClB,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,QAAQ,CAAC,KAA0E;QAC9F,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,QAAQ,IAAI,QAAQ,CAAC,WAAW,KAAK,CAAC;YAAE,OAAO,iBAAiB,CAAC;QACrE,MAAM,IAAI,CAAC,GAAG,CACZ
|
|
1
|
+
{"version":3,"file":"UserEmailDAO.js","sourceRoot":"","sources":["../src/UserEmailDAO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAwBvC,MAAM,OAAO,YAAa,SAAQ,OAAO;IACvC,YAAmB,QAAqB;QACtC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClB,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,QAAQ,CAAC,KAA0E;QAC9F,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,QAAQ,IAAI,QAAQ,CAAC,WAAW,KAAK,CAAC;YAAE,OAAO,iBAAiB,CAAC;QACrE,MAAM,IAAI,CAAC,GAAG,CACZ;;uGAEiG,EACjG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAC3D,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,KAAa;QAC5B,sEAAsE;QACtE,qEAAqE;QACrE,wBAAwB;QACxB,OAAO,IAAI,CAAC,KAAK,CAAe,mDAAmD,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC9G,CAAC;IAED,uEAAuE;IAChE,KAAK,CAAC,eAAe,CAAC,KAAa;QACxC,OAAO,IAAI,CAAC,KAAK,CAAe,uEAAuE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAClI,CAAC;IAEM,KAAK,CAAC,YAAY,CAAC,MAAc;QACtC,OAAO,IAAI,CAAC,GAAG,CAAe,uFAAuF,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IACnI,CAAC;IAED,+EAA+E;IACxE,KAAK,CAAC,MAAM,CAAC,KAAa;QAC/B,MAAM,IAAI,CAAC,GAAG,CAAC,wDAAwD,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAClG,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,iBAAiB,CAAC,MAAc,EAAE,WAAmB;QAChE,MAAM,IAAI,CAAC,GAAG,CAAC,yEAAyE,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACjI,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rexezuge/identity",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,15 +25,16 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@rexezuge/d1": "1.0.
|
|
29
|
-
"@rexezuge/errors": "1.0.
|
|
30
|
-
"@rexezuge/shared": "1.0.
|
|
28
|
+
"@rexezuge/d1": "1.0.2",
|
|
29
|
+
"@rexezuge/errors": "1.0.2",
|
|
30
|
+
"@rexezuge/shared": "1.0.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "26.6.4",
|
|
34
34
|
"typescript": "6.0.3",
|
|
35
35
|
"vitest": "4.1.11"
|
|
36
36
|
},
|
|
37
|
+
"description": "Account layer with address resolution, user DAOs, and canonical identity migrations.",
|
|
37
38
|
"scripts": {
|
|
38
39
|
"build": "tsc -p tsconfig.json",
|
|
39
40
|
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|