@weifuwujs/auth 0.9.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/README.md +172 -0
- package/dist/auth.d.ts +4 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +625 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +159 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/password.d.ts +21 -0
- package/dist/password.d.ts.map +1 -0
- package/dist/password.js +75 -0
- package/dist/password.js.map +1 -0
- package/dist/postgres.d.ts +22 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +240 -0
- package/dist/postgres.js.map +1 -0
- package/dist/sqlite.d.ts +3 -0
- package/dist/sqlite.d.ts.map +1 -0
- package/dist/sqlite.js +2 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/stores/memory.d.ts +4 -0
- package/dist/stores/memory.d.ts.map +1 -0
- package/dist/stores/memory.js +150 -0
- package/dist/stores/memory.js.map +1 -0
- package/dist/stores/sqlite.d.ts +13 -0
- package/dist/stores/sqlite.d.ts.map +1 -0
- package/dist/stores/sqlite.js +215 -0
- package/dist/stores/sqlite.js.map +1 -0
- package/dist/testing.d.ts +13 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +123 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -0
- package/templates/react-router-auth/app/auth.ts +13 -0
- package/templates/react-router-auth/app/context.ts +6 -0
- package/templates/react-router-auth/app/routes/account.tsx +47 -0
- package/templates/react-router-auth/app/routes/login.tsx +59 -0
- package/templates/react-router-auth/app/routes/register.tsx +47 -0
- package/templates/react-router-auth/app/routes/reset-password.tsx +54 -0
- package/templates/react-router-auth/app/routes.ts +9 -0
- package/templates/react-router-auth/app/server.ts +31 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
function emailTaken() {
|
|
3
|
+
return Object.assign(new Error('email_taken'), { code: 'email_taken' });
|
|
4
|
+
}
|
|
5
|
+
function cloneUser(user) {
|
|
6
|
+
return { ...user };
|
|
7
|
+
}
|
|
8
|
+
function cloneSession(session) {
|
|
9
|
+
return { ...session };
|
|
10
|
+
}
|
|
11
|
+
/** 内存实现:测试默认,也可用于单进程开发 */
|
|
12
|
+
export function memoryAuthStore() {
|
|
13
|
+
const users = new Map();
|
|
14
|
+
const userIdsByEmail = new Map();
|
|
15
|
+
const sessions = new Map();
|
|
16
|
+
const resetTokens = new Map();
|
|
17
|
+
return {
|
|
18
|
+
async createUser({ email, passwordHash, role = 'user' }) {
|
|
19
|
+
if (userIdsByEmail.has(email))
|
|
20
|
+
throw emailTaken();
|
|
21
|
+
const timestamp = new Date();
|
|
22
|
+
const user = {
|
|
23
|
+
id: randomUUID(),
|
|
24
|
+
email,
|
|
25
|
+
passwordHash,
|
|
26
|
+
role,
|
|
27
|
+
disabledAt: null,
|
|
28
|
+
createdAt: timestamp,
|
|
29
|
+
updatedAt: timestamp,
|
|
30
|
+
};
|
|
31
|
+
users.set(user.id, user);
|
|
32
|
+
userIdsByEmail.set(email, user.id);
|
|
33
|
+
return cloneUser(user);
|
|
34
|
+
},
|
|
35
|
+
async findUserByEmail(email) {
|
|
36
|
+
const id = userIdsByEmail.get(email);
|
|
37
|
+
const user = id ? users.get(id) : undefined;
|
|
38
|
+
return user ? cloneUser(user) : null;
|
|
39
|
+
},
|
|
40
|
+
async findUserById(id) {
|
|
41
|
+
const user = users.get(id);
|
|
42
|
+
return user ? cloneUser(user) : null;
|
|
43
|
+
},
|
|
44
|
+
async listUsers() {
|
|
45
|
+
return [...users.values()]
|
|
46
|
+
.map(cloneUser)
|
|
47
|
+
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
48
|
+
},
|
|
49
|
+
async updateUserEmail(id, email) {
|
|
50
|
+
const user = users.get(id);
|
|
51
|
+
if (!user)
|
|
52
|
+
return;
|
|
53
|
+
const existing = userIdsByEmail.get(email);
|
|
54
|
+
if (existing !== undefined && existing !== id)
|
|
55
|
+
throw emailTaken();
|
|
56
|
+
userIdsByEmail.delete(user.email);
|
|
57
|
+
user.email = email;
|
|
58
|
+
user.updatedAt = new Date();
|
|
59
|
+
userIdsByEmail.set(email, id);
|
|
60
|
+
},
|
|
61
|
+
async updateUserPassword(id, passwordHash) {
|
|
62
|
+
const user = users.get(id);
|
|
63
|
+
if (!user)
|
|
64
|
+
return;
|
|
65
|
+
user.passwordHash = passwordHash;
|
|
66
|
+
user.updatedAt = new Date();
|
|
67
|
+
},
|
|
68
|
+
async setUserRole(id, role) {
|
|
69
|
+
const user = users.get(id);
|
|
70
|
+
if (!user)
|
|
71
|
+
return;
|
|
72
|
+
user.role = role;
|
|
73
|
+
user.updatedAt = new Date();
|
|
74
|
+
},
|
|
75
|
+
async setUserDisabled(id, disabledAt) {
|
|
76
|
+
const user = users.get(id);
|
|
77
|
+
if (!user)
|
|
78
|
+
return;
|
|
79
|
+
user.disabledAt = disabledAt;
|
|
80
|
+
user.updatedAt = new Date();
|
|
81
|
+
},
|
|
82
|
+
async createSession({ userId, tokenHash, expiresAt }) {
|
|
83
|
+
const timestamp = new Date();
|
|
84
|
+
const session = {
|
|
85
|
+
id: randomUUID(),
|
|
86
|
+
userId,
|
|
87
|
+
tokenHash,
|
|
88
|
+
createdAt: timestamp,
|
|
89
|
+
expiresAt,
|
|
90
|
+
lastSeenAt: timestamp,
|
|
91
|
+
};
|
|
92
|
+
sessions.set(session.id, session);
|
|
93
|
+
return cloneSession(session);
|
|
94
|
+
},
|
|
95
|
+
async findSessionByTokenHash(tokenHash) {
|
|
96
|
+
for (const session of sessions.values()) {
|
|
97
|
+
if (session.tokenHash === tokenHash)
|
|
98
|
+
return cloneSession(session);
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
},
|
|
102
|
+
async touchSession(id, lastSeenAt) {
|
|
103
|
+
const session = sessions.get(id);
|
|
104
|
+
if (session)
|
|
105
|
+
session.lastSeenAt = lastSeenAt;
|
|
106
|
+
},
|
|
107
|
+
async deleteSession(id) {
|
|
108
|
+
sessions.delete(id);
|
|
109
|
+
},
|
|
110
|
+
async deleteUserSessions(userId, exceptId) {
|
|
111
|
+
for (const [id, session] of sessions) {
|
|
112
|
+
if (session.userId === userId && id !== exceptId)
|
|
113
|
+
sessions.delete(id);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
async deleteExpiredSessions(now) {
|
|
117
|
+
let count = 0;
|
|
118
|
+
for (const [id, session] of sessions) {
|
|
119
|
+
if (session.expiresAt.getTime() <= now.getTime()) {
|
|
120
|
+
sessions.delete(id);
|
|
121
|
+
count++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return count;
|
|
125
|
+
},
|
|
126
|
+
async createResetToken({ userId, tokenHash, expiresAt }) {
|
|
127
|
+
resetTokens.set(tokenHash, { userId, expiresAt, usedAt: null });
|
|
128
|
+
},
|
|
129
|
+
async consumeResetToken(tokenHash, now) {
|
|
130
|
+
const entry = resetTokens.get(tokenHash);
|
|
131
|
+
if (!entry || entry.usedAt)
|
|
132
|
+
return null;
|
|
133
|
+
if (entry.expiresAt.getTime() <= now.getTime())
|
|
134
|
+
return null;
|
|
135
|
+
entry.usedAt = now;
|
|
136
|
+
return entry.userId;
|
|
137
|
+
},
|
|
138
|
+
async deleteExpiredResetTokens(now) {
|
|
139
|
+
let count = 0;
|
|
140
|
+
for (const [hash, entry] of resetTokens) {
|
|
141
|
+
if (entry.expiresAt.getTime() <= now.getTime()) {
|
|
142
|
+
resetTokens.delete(hash);
|
|
143
|
+
count++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return count;
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { AuthStore } from '../types.ts';
|
|
3
|
+
export interface SqliteAuthStore extends AuthStore {
|
|
4
|
+
readonly db: DatabaseSync;
|
|
5
|
+
migrate(): Promise<void>;
|
|
6
|
+
close(): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* sqlite 实现(`node:sqlite`,零依赖零配置)。构造时自动建表,
|
|
10
|
+
* 迁移幂等可重复执行;`:memory:` 用于测试。
|
|
11
|
+
*/
|
|
12
|
+
export declare function sqliteAuthStore(path?: string): SqliteAuthStore;
|
|
13
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/stores/sqlite.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,KAAK,EAAe,SAAS,EAAY,MAAM,aAAa,CAAA;AAqGnE,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAA;IACzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAiB,GAAG,eAAe,CA4LtE"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
const SCHEMA = `
|
|
6
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
7
|
+
id TEXT PRIMARY KEY,
|
|
8
|
+
email TEXT NOT NULL UNIQUE,
|
|
9
|
+
password_hash TEXT NOT NULL,
|
|
10
|
+
role TEXT NOT NULL DEFAULT 'user',
|
|
11
|
+
disabled_at TEXT,
|
|
12
|
+
created_at TEXT NOT NULL,
|
|
13
|
+
updated_at TEXT NOT NULL
|
|
14
|
+
);
|
|
15
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
18
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
19
|
+
created_at TEXT NOT NULL,
|
|
20
|
+
expires_at TEXT NOT NULL,
|
|
21
|
+
last_seen_at TEXT NOT NULL
|
|
22
|
+
);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS sessions_user_id_idx ON sessions(user_id);
|
|
24
|
+
CREATE TABLE IF NOT EXISTS password_resets (
|
|
25
|
+
token_hash TEXT PRIMARY KEY,
|
|
26
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
27
|
+
expires_at TEXT NOT NULL,
|
|
28
|
+
used_at TEXT
|
|
29
|
+
);
|
|
30
|
+
`;
|
|
31
|
+
function applyMigrations(db) {
|
|
32
|
+
db.exec('CREATE TABLE IF NOT EXISTS auth_migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)');
|
|
33
|
+
const applied = db
|
|
34
|
+
.prepare('SELECT version FROM auth_migrations WHERE version = ?')
|
|
35
|
+
.get(1);
|
|
36
|
+
if (applied)
|
|
37
|
+
return;
|
|
38
|
+
db.exec('BEGIN');
|
|
39
|
+
try {
|
|
40
|
+
db.exec(SCHEMA);
|
|
41
|
+
db.prepare('INSERT INTO auth_migrations (version, applied_at) VALUES (?, ?)').run(1, new Date().toISOString());
|
|
42
|
+
db.exec('COMMIT');
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
db.exec('ROLLBACK');
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function uniqueViolation(error, code) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
if (/UNIQUE|constraint/i.test(message)) {
|
|
52
|
+
throw Object.assign(new Error(code), { code });
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
function toUser(row) {
|
|
57
|
+
return {
|
|
58
|
+
id: row.id,
|
|
59
|
+
email: row.email,
|
|
60
|
+
passwordHash: row.password_hash,
|
|
61
|
+
role: row.role === 'admin' ? 'admin' : 'user',
|
|
62
|
+
disabledAt: row.disabled_at ? new Date(row.disabled_at) : null,
|
|
63
|
+
createdAt: new Date(row.created_at),
|
|
64
|
+
updatedAt: new Date(row.updated_at),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function toSession(row) {
|
|
68
|
+
return {
|
|
69
|
+
id: row.id,
|
|
70
|
+
userId: row.user_id,
|
|
71
|
+
tokenHash: row.token_hash,
|
|
72
|
+
createdAt: new Date(row.created_at),
|
|
73
|
+
expiresAt: new Date(row.expires_at),
|
|
74
|
+
lastSeenAt: new Date(row.last_seen_at),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* sqlite 实现(`node:sqlite`,零依赖零配置)。构造时自动建表,
|
|
79
|
+
* 迁移幂等可重复执行;`:memory:` 用于测试。
|
|
80
|
+
*/
|
|
81
|
+
export function sqliteAuthStore(path = 'data/auth.db') {
|
|
82
|
+
if (path !== ':memory:')
|
|
83
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
84
|
+
const db = new DatabaseSync(path);
|
|
85
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
86
|
+
applyMigrations(db);
|
|
87
|
+
return {
|
|
88
|
+
db,
|
|
89
|
+
async migrate() {
|
|
90
|
+
applyMigrations(db);
|
|
91
|
+
},
|
|
92
|
+
close() {
|
|
93
|
+
db.close();
|
|
94
|
+
},
|
|
95
|
+
async createUser({ email, passwordHash, role = 'user' }) {
|
|
96
|
+
const timestamp = new Date().toISOString();
|
|
97
|
+
const id = randomUUID();
|
|
98
|
+
try {
|
|
99
|
+
db.prepare(`INSERT INTO users (id, email, password_hash, role, disabled_at, created_at, updated_at)
|
|
100
|
+
VALUES (?, ?, ?, ?, NULL, ?, ?)`).run(id, email, passwordHash, role, timestamp, timestamp);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
uniqueViolation(error, 'email_taken');
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
id,
|
|
107
|
+
email,
|
|
108
|
+
passwordHash,
|
|
109
|
+
role,
|
|
110
|
+
disabledAt: null,
|
|
111
|
+
createdAt: new Date(timestamp),
|
|
112
|
+
updatedAt: new Date(timestamp),
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
async findUserByEmail(email) {
|
|
116
|
+
const row = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
|
|
117
|
+
return row ? toUser(row) : null;
|
|
118
|
+
},
|
|
119
|
+
async findUserById(id) {
|
|
120
|
+
const row = db.prepare('SELECT * FROM users WHERE id = ?').get(id);
|
|
121
|
+
return row ? toUser(row) : null;
|
|
122
|
+
},
|
|
123
|
+
async listUsers() {
|
|
124
|
+
const rows = db
|
|
125
|
+
.prepare('SELECT * FROM users ORDER BY created_at')
|
|
126
|
+
.all();
|
|
127
|
+
return rows.map(toUser);
|
|
128
|
+
},
|
|
129
|
+
async updateUserEmail(id, email) {
|
|
130
|
+
try {
|
|
131
|
+
db.prepare('UPDATE users SET email = ?, updated_at = ? WHERE id = ?').run(email, new Date().toISOString(), id);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
uniqueViolation(error, 'email_taken');
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
async updateUserPassword(id, passwordHash) {
|
|
138
|
+
db.prepare('UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?').run(passwordHash, new Date().toISOString(), id);
|
|
139
|
+
},
|
|
140
|
+
async setUserRole(id, role) {
|
|
141
|
+
db.prepare('UPDATE users SET role = ?, updated_at = ? WHERE id = ?').run(role, new Date().toISOString(), id);
|
|
142
|
+
},
|
|
143
|
+
async setUserDisabled(id, disabledAt) {
|
|
144
|
+
db.prepare('UPDATE users SET disabled_at = ?, updated_at = ? WHERE id = ?').run(disabledAt ? disabledAt.toISOString() : null, new Date().toISOString(), id);
|
|
145
|
+
},
|
|
146
|
+
async createSession({ userId, tokenHash, expiresAt }) {
|
|
147
|
+
const timestamp = new Date().toISOString();
|
|
148
|
+
const session = {
|
|
149
|
+
id: randomUUID(),
|
|
150
|
+
userId,
|
|
151
|
+
tokenHash,
|
|
152
|
+
createdAt: new Date(timestamp),
|
|
153
|
+
expiresAt,
|
|
154
|
+
lastSeenAt: new Date(timestamp),
|
|
155
|
+
};
|
|
156
|
+
db.prepare(`INSERT INTO sessions (id, user_id, token_hash, created_at, expires_at, last_seen_at)
|
|
157
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(session.id, userId, tokenHash, timestamp, expiresAt.toISOString(), timestamp);
|
|
158
|
+
return session;
|
|
159
|
+
},
|
|
160
|
+
async findSessionByTokenHash(tokenHash) {
|
|
161
|
+
const row = db
|
|
162
|
+
.prepare('SELECT * FROM sessions WHERE token_hash = ?')
|
|
163
|
+
.get(tokenHash);
|
|
164
|
+
return row ? toSession(row) : null;
|
|
165
|
+
},
|
|
166
|
+
async touchSession(id, lastSeenAt) {
|
|
167
|
+
db.prepare('UPDATE sessions SET last_seen_at = ? WHERE id = ?').run(lastSeenAt.toISOString(), id);
|
|
168
|
+
},
|
|
169
|
+
async deleteSession(id) {
|
|
170
|
+
db.prepare('DELETE FROM sessions WHERE id = ?').run(id);
|
|
171
|
+
},
|
|
172
|
+
async deleteUserSessions(userId, exceptId) {
|
|
173
|
+
if (exceptId) {
|
|
174
|
+
db.prepare('DELETE FROM sessions WHERE user_id = ? AND id <> ?').run(userId, exceptId);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
db.prepare('DELETE FROM sessions WHERE user_id = ?').run(userId);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
async deleteExpiredSessions(now) {
|
|
181
|
+
const result = db
|
|
182
|
+
.prepare('DELETE FROM sessions WHERE expires_at <= ?')
|
|
183
|
+
.run(now.toISOString());
|
|
184
|
+
return Number(result.changes);
|
|
185
|
+
},
|
|
186
|
+
async createResetToken({ userId, tokenHash, expiresAt }) {
|
|
187
|
+
try {
|
|
188
|
+
db.prepare('INSERT INTO password_resets (token_hash, user_id, expires_at, used_at) VALUES (?, ?, ?, NULL)').run(tokenHash, userId, expiresAt.toISOString());
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
uniqueViolation(error, 'invalid_token');
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
async consumeResetToken(tokenHash, now) {
|
|
195
|
+
const timestamp = now.toISOString();
|
|
196
|
+
const result = db
|
|
197
|
+
.prepare(`UPDATE password_resets SET used_at = ?
|
|
198
|
+
WHERE token_hash = ? AND used_at IS NULL AND expires_at > ?`)
|
|
199
|
+
.run(timestamp, tokenHash, timestamp);
|
|
200
|
+
if (Number(result.changes) === 0)
|
|
201
|
+
return null;
|
|
202
|
+
const row = db
|
|
203
|
+
.prepare('SELECT user_id FROM password_resets WHERE token_hash = ?')
|
|
204
|
+
.get(tokenHash);
|
|
205
|
+
return row?.user_id ?? null;
|
|
206
|
+
},
|
|
207
|
+
async deleteExpiredResetTokens(now) {
|
|
208
|
+
const result = db
|
|
209
|
+
.prepare('DELETE FROM password_resets WHERE expires_at <= ?')
|
|
210
|
+
.run(now.toISOString());
|
|
211
|
+
return Number(result.changes);
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/stores/sqlite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AuthStore } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* 存储契约套件:memory / sqlite / postgres 与自定义 store 共用。
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { authStoreContract } from '@weifuwujs/auth/testing'
|
|
7
|
+
* authStoreContract('my-store', () => myAuthStore())
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export declare function authStoreContract(name: string, create: () => AuthStore | Promise<AuthStore>, options?: {
|
|
11
|
+
skip?: boolean | string;
|
|
12
|
+
}): void;
|
|
13
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAW3C;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,EAC5C,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAAO,GACxC,IAAI,CAqIN"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, test } from 'node:test';
|
|
3
|
+
async function emailTaken(store, email) {
|
|
4
|
+
await assert.rejects(() => store.createUser({ email, passwordHash: 'x' }), (error) => error.code === 'email_taken' ||
|
|
5
|
+
/email_taken|UNIQUE|constraint/i.test(String(error)));
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 存储契约套件:memory / sqlite / postgres 与自定义 store 共用。
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { authStoreContract } from '@weifuwujs/auth/testing'
|
|
12
|
+
* authStoreContract('my-store', () => myAuthStore())
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function authStoreContract(name, create, options = {}) {
|
|
16
|
+
describe(`auth store contract (${name})`, { skip: options.skip }, () => {
|
|
17
|
+
test('user CRUD 与唯一 email', async () => {
|
|
18
|
+
const store = await create();
|
|
19
|
+
const user = await store.createUser({
|
|
20
|
+
email: 'ada@example.com',
|
|
21
|
+
passwordHash: 'hash-1',
|
|
22
|
+
});
|
|
23
|
+
assert.ok(user.id);
|
|
24
|
+
assert.equal(user.email, 'ada@example.com');
|
|
25
|
+
assert.equal(user.role, 'user');
|
|
26
|
+
assert.equal(user.disabledAt, null);
|
|
27
|
+
assert.ok(user.createdAt instanceof Date);
|
|
28
|
+
assert.deepEqual((await store.findUserByEmail('ada@example.com'))?.id, user.id);
|
|
29
|
+
assert.equal(await store.findUserByEmail('nobody@example.com'), null);
|
|
30
|
+
assert.deepEqual((await store.findUserById(user.id))?.email, 'ada@example.com');
|
|
31
|
+
assert.equal(await store.findUserById('missing'), null);
|
|
32
|
+
await emailTaken(store, 'ada@example.com');
|
|
33
|
+
await store.updateUserEmail(user.id, 'ada2@example.com');
|
|
34
|
+
assert.equal(await store.findUserByEmail('ada@example.com'), null);
|
|
35
|
+
assert.deepEqual((await store.findUserByEmail('ada2@example.com'))?.id, user.id);
|
|
36
|
+
await store.updateUserPassword(user.id, 'hash-2');
|
|
37
|
+
assert.equal((await store.findUserById(user.id))?.passwordHash, 'hash-2');
|
|
38
|
+
await store.setUserRole(user.id, 'admin');
|
|
39
|
+
assert.equal((await store.findUserById(user.id))?.role, 'admin');
|
|
40
|
+
const disabledAt = new Date('2026-01-01T00:00:00.000Z');
|
|
41
|
+
await store.setUserDisabled(user.id, disabledAt);
|
|
42
|
+
assert.equal((await store.findUserById(user.id))?.disabledAt?.toISOString(), disabledAt.toISOString());
|
|
43
|
+
});
|
|
44
|
+
test('session 生命周期与过期清理', async () => {
|
|
45
|
+
const store = await create();
|
|
46
|
+
const user = await store.createUser({
|
|
47
|
+
email: 's@example.com',
|
|
48
|
+
passwordHash: 'hash',
|
|
49
|
+
});
|
|
50
|
+
const other = await store.createUser({
|
|
51
|
+
email: 'o@example.com',
|
|
52
|
+
passwordHash: 'hash',
|
|
53
|
+
});
|
|
54
|
+
const session = await store.createSession({
|
|
55
|
+
userId: user.id,
|
|
56
|
+
tokenHash: 'token-1',
|
|
57
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
58
|
+
});
|
|
59
|
+
assert.ok(session.id);
|
|
60
|
+
assert.equal(session.userId, user.id);
|
|
61
|
+
assert.deepEqual((await store.findSessionByTokenHash('token-1'))?.id, session.id);
|
|
62
|
+
assert.equal(await store.findSessionByTokenHash('missing'), null);
|
|
63
|
+
const touched = new Date(Date.now() + 1000);
|
|
64
|
+
await store.touchSession(session.id, touched);
|
|
65
|
+
assert.equal((await store.findSessionByTokenHash('token-1'))?.lastSeenAt.getTime(), touched.getTime());
|
|
66
|
+
// 第二个会话 + 其他用户的会话
|
|
67
|
+
const keep = await store.createSession({
|
|
68
|
+
userId: user.id,
|
|
69
|
+
tokenHash: 'token-2',
|
|
70
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
71
|
+
});
|
|
72
|
+
await store.createSession({
|
|
73
|
+
userId: other.id,
|
|
74
|
+
tokenHash: 'token-3',
|
|
75
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
76
|
+
});
|
|
77
|
+
await store.deleteUserSessions(user.id, keep.id);
|
|
78
|
+
assert.equal(await store.findSessionByTokenHash('token-1'), null);
|
|
79
|
+
assert.ok(await store.findSessionByTokenHash('token-2'));
|
|
80
|
+
assert.ok(await store.findSessionByTokenHash('token-3'));
|
|
81
|
+
await store.deleteSession(keep.id);
|
|
82
|
+
assert.equal(await store.findSessionByTokenHash('token-2'), null);
|
|
83
|
+
await store.createSession({
|
|
84
|
+
userId: user.id,
|
|
85
|
+
tokenHash: 'token-expired',
|
|
86
|
+
expiresAt: new Date(Date.now() - 1000),
|
|
87
|
+
});
|
|
88
|
+
const removed = await store.deleteExpiredSessions(new Date());
|
|
89
|
+
assert.ok(removed >= 1);
|
|
90
|
+
assert.equal(await store.findSessionByTokenHash('token-expired'), null);
|
|
91
|
+
assert.ok(await store.findSessionByTokenHash('token-3'));
|
|
92
|
+
});
|
|
93
|
+
test('reset token 单次消费与过期', async () => {
|
|
94
|
+
const store = await create();
|
|
95
|
+
const user = await store.createUser({
|
|
96
|
+
email: 'r@example.com',
|
|
97
|
+
passwordHash: 'hash',
|
|
98
|
+
});
|
|
99
|
+
await store.createResetToken({
|
|
100
|
+
userId: user.id,
|
|
101
|
+
tokenHash: 'reset-1',
|
|
102
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
103
|
+
});
|
|
104
|
+
assert.equal(await store.consumeResetToken('reset-1', new Date()), user.id);
|
|
105
|
+
assert.equal(await store.consumeResetToken('reset-1', new Date()), null, '不得二次消费');
|
|
106
|
+
await store.createResetToken({
|
|
107
|
+
userId: user.id,
|
|
108
|
+
tokenHash: 'reset-expired',
|
|
109
|
+
expiresAt: new Date(Date.now() - 1000),
|
|
110
|
+
});
|
|
111
|
+
assert.equal(await store.consumeResetToken('reset-expired', new Date()), null);
|
|
112
|
+
await store.createResetToken({
|
|
113
|
+
userId: user.id,
|
|
114
|
+
tokenHash: 'reset-old',
|
|
115
|
+
expiresAt: new Date(Date.now() - 1000),
|
|
116
|
+
});
|
|
117
|
+
const removed = await store.deleteExpiredResetTokens(new Date());
|
|
118
|
+
assert.ok(removed >= 1);
|
|
119
|
+
assert.equal(await store.consumeResetToken('reset-old', new Date()), null);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|