@emdash-cms/auth 0.0.1

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 (48) hide show
  1. package/dist/adapters/kysely.d.mts +62 -0
  2. package/dist/adapters/kysely.d.mts.map +1 -0
  3. package/dist/adapters/kysely.mjs +379 -0
  4. package/dist/adapters/kysely.mjs.map +1 -0
  5. package/dist/authenticate-D5UgaoTH.d.mts +124 -0
  6. package/dist/authenticate-D5UgaoTH.d.mts.map +1 -0
  7. package/dist/authenticate-j5GayLXB.mjs +373 -0
  8. package/dist/authenticate-j5GayLXB.mjs.map +1 -0
  9. package/dist/index.d.mts +444 -0
  10. package/dist/index.d.mts.map +1 -0
  11. package/dist/index.mjs +728 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/dist/oauth/providers/github.d.mts +12 -0
  14. package/dist/oauth/providers/github.d.mts.map +1 -0
  15. package/dist/oauth/providers/github.mjs +55 -0
  16. package/dist/oauth/providers/github.mjs.map +1 -0
  17. package/dist/oauth/providers/google.d.mts +7 -0
  18. package/dist/oauth/providers/google.d.mts.map +1 -0
  19. package/dist/oauth/providers/google.mjs +38 -0
  20. package/dist/oauth/providers/google.mjs.map +1 -0
  21. package/dist/passkey/index.d.mts +2 -0
  22. package/dist/passkey/index.mjs +3 -0
  23. package/dist/types-Bu4irX9A.d.mts +35 -0
  24. package/dist/types-Bu4irX9A.d.mts.map +1 -0
  25. package/dist/types-CiSNpRI9.mjs +60 -0
  26. package/dist/types-CiSNpRI9.mjs.map +1 -0
  27. package/dist/types-HtRc90Wi.d.mts +208 -0
  28. package/dist/types-HtRc90Wi.d.mts.map +1 -0
  29. package/package.json +72 -0
  30. package/src/adapters/kysely.ts +715 -0
  31. package/src/config.ts +214 -0
  32. package/src/index.ts +135 -0
  33. package/src/invite.ts +205 -0
  34. package/src/magic-link/index.ts +150 -0
  35. package/src/oauth/consumer.ts +324 -0
  36. package/src/oauth/providers/github.ts +68 -0
  37. package/src/oauth/providers/google.ts +34 -0
  38. package/src/oauth/types.ts +36 -0
  39. package/src/passkey/authenticate.ts +183 -0
  40. package/src/passkey/index.ts +27 -0
  41. package/src/passkey/register.ts +232 -0
  42. package/src/passkey/types.ts +120 -0
  43. package/src/rbac.test.ts +141 -0
  44. package/src/rbac.ts +205 -0
  45. package/src/signup.ts +210 -0
  46. package/src/tokens.test.ts +141 -0
  47. package/src/tokens.ts +238 -0
  48. package/src/types.ts +352 -0
package/src/types.ts ADDED
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Core types for @emdash-cms/auth
3
+ */
4
+
5
+ // ============================================================================
6
+ // Roles & Permissions
7
+ // ============================================================================
8
+
9
+ export const Role = {
10
+ SUBSCRIBER: 10,
11
+ CONTRIBUTOR: 20,
12
+ AUTHOR: 30,
13
+ EDITOR: 40,
14
+ ADMIN: 50,
15
+ } as const;
16
+
17
+ export type RoleLevel = (typeof Role)[keyof typeof Role];
18
+ export type RoleName = keyof typeof Role;
19
+
20
+ export function roleFromLevel(level: number): RoleName | undefined {
21
+ const entry = Object.entries(Role).find(([, v]) => v === level);
22
+ if (!entry) return undefined;
23
+ const name = entry[0];
24
+ if (isRoleName(name)) return name;
25
+ return undefined;
26
+ }
27
+
28
+ function isRoleName(value: string): value is RoleName {
29
+ return value in Role;
30
+ }
31
+
32
+ const ROLE_LEVEL_MAP = new Map<number, RoleLevel>(Object.values(Role).map((v) => [v, v]));
33
+
34
+ export function toRoleLevel(value: number): RoleLevel {
35
+ const level = ROLE_LEVEL_MAP.get(value);
36
+ if (level !== undefined) return level;
37
+ throw new Error(`Invalid role level: ${value}`);
38
+ }
39
+
40
+ const DEVICE_TYPE_MAP: Record<string, DeviceType | undefined> = {
41
+ singleDevice: "singleDevice",
42
+ multiDevice: "multiDevice",
43
+ };
44
+
45
+ export function toDeviceType(value: string): DeviceType {
46
+ const dt = DEVICE_TYPE_MAP[value];
47
+ if (dt !== undefined) return dt;
48
+ throw new Error(`Invalid device type: ${value}`);
49
+ }
50
+
51
+ const TOKEN_TYPE_MAP: Record<string, TokenType | undefined> = {
52
+ magic_link: "magic_link",
53
+ email_verify: "email_verify",
54
+ invite: "invite",
55
+ recovery: "recovery",
56
+ };
57
+
58
+ export function toTokenType(value: string): TokenType {
59
+ const tt = TOKEN_TYPE_MAP[value];
60
+ if (tt !== undefined) return tt;
61
+ throw new Error(`Invalid token type: ${value}`);
62
+ }
63
+
64
+ export function roleToLevel(name: RoleName): RoleLevel {
65
+ return Role[name];
66
+ }
67
+
68
+ // ============================================================================
69
+ // User
70
+ // ============================================================================
71
+
72
+ export interface User {
73
+ id: string;
74
+ email: string;
75
+ name: string | null;
76
+ avatarUrl: string | null;
77
+ role: RoleLevel;
78
+ emailVerified: boolean;
79
+ disabled: boolean;
80
+ data: Record<string, unknown> | null;
81
+ createdAt: Date;
82
+ updatedAt: Date;
83
+ }
84
+
85
+ export interface NewUser {
86
+ email: string;
87
+ name?: string | null;
88
+ avatarUrl?: string | null;
89
+ role?: RoleLevel;
90
+ emailVerified?: boolean;
91
+ data?: Record<string, unknown> | null;
92
+ }
93
+
94
+ export interface UpdateUser {
95
+ email?: string;
96
+ name?: string | null;
97
+ avatarUrl?: string | null;
98
+ role?: RoleLevel;
99
+ emailVerified?: boolean;
100
+ disabled?: boolean;
101
+ data?: Record<string, unknown> | null;
102
+ }
103
+
104
+ // ============================================================================
105
+ // Credentials (Passkeys)
106
+ // ============================================================================
107
+
108
+ export type AuthenticatorTransport = "usb" | "nfc" | "ble" | "internal" | "hybrid";
109
+ export type DeviceType = "singleDevice" | "multiDevice";
110
+
111
+ export interface Credential {
112
+ id: string; // Base64url credential ID
113
+ userId: string;
114
+ publicKey: Uint8Array; // COSE public key
115
+ counter: number;
116
+ deviceType: DeviceType;
117
+ backedUp: boolean;
118
+ transports: AuthenticatorTransport[];
119
+ name: string | null;
120
+ createdAt: Date;
121
+ lastUsedAt: Date;
122
+ }
123
+
124
+ export interface NewCredential {
125
+ id: string;
126
+ userId: string;
127
+ publicKey: Uint8Array;
128
+ counter: number;
129
+ deviceType: DeviceType;
130
+ backedUp: boolean;
131
+ transports: AuthenticatorTransport[];
132
+ name?: string | null;
133
+ }
134
+
135
+ // ============================================================================
136
+ // Sessions
137
+ // ============================================================================
138
+
139
+ export interface Session {
140
+ id: string;
141
+ userId: string;
142
+ expiresAt: Date;
143
+ ipAddress: string | null;
144
+ userAgent: string | null;
145
+ createdAt: Date;
146
+ }
147
+
148
+ export interface SessionData {
149
+ userId: string;
150
+ expiresAt: number; // Unix timestamp
151
+ }
152
+
153
+ // ============================================================================
154
+ // Auth Tokens (magic links, invites, etc.)
155
+ // ============================================================================
156
+
157
+ export type TokenType = "magic_link" | "email_verify" | "invite" | "recovery";
158
+
159
+ export interface AuthToken {
160
+ hash: string; // SHA-256 hash of the raw token
161
+ userId: string | null; // null for pre-user tokens (invite/signup)
162
+ email: string | null; // For pre-user tokens
163
+ type: TokenType;
164
+ role: RoleLevel | null; // For invites
165
+ invitedBy: string | null;
166
+ expiresAt: Date;
167
+ createdAt: Date;
168
+ }
169
+
170
+ export interface NewAuthToken {
171
+ hash: string;
172
+ userId?: string | null;
173
+ email?: string | null;
174
+ type: TokenType;
175
+ role?: RoleLevel | null;
176
+ invitedBy?: string | null;
177
+ expiresAt: Date;
178
+ }
179
+
180
+ // ============================================================================
181
+ // OAuth Accounts
182
+ // ============================================================================
183
+
184
+ export interface OAuthAccount {
185
+ provider: string;
186
+ providerAccountId: string;
187
+ userId: string;
188
+ createdAt: Date;
189
+ }
190
+
191
+ export interface NewOAuthAccount {
192
+ provider: string;
193
+ providerAccountId: string;
194
+ userId: string;
195
+ }
196
+
197
+ // ============================================================================
198
+ // OAuth Connections (SSO config)
199
+ // ============================================================================
200
+
201
+ export interface OAuthConnection {
202
+ id: string;
203
+ name: string;
204
+ provider: "oidc" | "github" | "google";
205
+ clientId: string;
206
+ clientSecretEnc: string; // Encrypted
207
+ issuerUrl: string | null;
208
+ config: Record<string, unknown> | null;
209
+ enabled: boolean;
210
+ createdAt: Date;
211
+ }
212
+
213
+ // ============================================================================
214
+ // OAuth Clients (when EmDash is provider)
215
+ // ============================================================================
216
+
217
+ export interface OAuthClient {
218
+ id: string;
219
+ name: string;
220
+ secretHash: string;
221
+ redirectUris: string[];
222
+ scopes: string[];
223
+ createdAt: Date;
224
+ }
225
+
226
+ // ============================================================================
227
+ // Allowed Domains (self-signup)
228
+ // ============================================================================
229
+
230
+ export interface AllowedDomain {
231
+ domain: string;
232
+ defaultRole: RoleLevel;
233
+ enabled: boolean;
234
+ createdAt: Date;
235
+ }
236
+
237
+ // ============================================================================
238
+ // User Listing Types (for admin UI)
239
+ // ============================================================================
240
+
241
+ /** Extended user with list view computed fields */
242
+ export interface UserListItem extends User {
243
+ lastLogin: Date | null;
244
+ credentialCount: number;
245
+ oauthProviders: string[];
246
+ }
247
+
248
+ /** User with full details including related data */
249
+ export interface UserWithDetails {
250
+ user: User;
251
+ credentials: Credential[];
252
+ oauthAccounts: OAuthAccount[];
253
+ lastLogin: Date | null;
254
+ }
255
+
256
+ // ============================================================================
257
+ // Auth Adapter Interface
258
+ // ============================================================================
259
+
260
+ export interface AuthAdapter {
261
+ // Users
262
+ getUserById(id: string): Promise<User | null>;
263
+ getUserByEmail(email: string): Promise<User | null>;
264
+ createUser(user: NewUser): Promise<User>;
265
+ updateUser(id: string, data: UpdateUser): Promise<void>;
266
+ deleteUser(id: string): Promise<void>;
267
+ countUsers(): Promise<number>;
268
+
269
+ // User listing and details (for admin)
270
+ getUsers(options?: {
271
+ search?: string;
272
+ role?: number;
273
+ cursor?: string;
274
+ limit?: number;
275
+ }): Promise<{ items: UserListItem[]; nextCursor?: string }>;
276
+ getUserWithDetails(id: string): Promise<UserWithDetails | null>;
277
+ countAdmins(): Promise<number>;
278
+
279
+ // Credentials
280
+ getCredentialById(id: string): Promise<Credential | null>;
281
+ getCredentialsByUserId(userId: string): Promise<Credential[]>;
282
+ createCredential(credential: NewCredential): Promise<Credential>;
283
+ updateCredentialCounter(id: string, counter: number): Promise<void>;
284
+ updateCredentialName(id: string, name: string | null): Promise<void>;
285
+ deleteCredential(id: string): Promise<void>;
286
+ countCredentialsByUserId(userId: string): Promise<number>;
287
+
288
+ // Auth Tokens
289
+ createToken(token: NewAuthToken): Promise<void>;
290
+ getToken(hash: string, type: TokenType): Promise<AuthToken | null>;
291
+ deleteToken(hash: string): Promise<void>;
292
+ deleteExpiredTokens(): Promise<void>;
293
+
294
+ // OAuth Accounts
295
+ getOAuthAccount(provider: string, providerAccountId: string): Promise<OAuthAccount | null>;
296
+ getOAuthAccountsByUserId(userId: string): Promise<OAuthAccount[]>;
297
+ createOAuthAccount(account: NewOAuthAccount): Promise<OAuthAccount>;
298
+ deleteOAuthAccount(provider: string, providerAccountId: string): Promise<void>;
299
+
300
+ // Allowed Domains
301
+ getAllowedDomain(domain: string): Promise<AllowedDomain | null>;
302
+ getAllowedDomains(): Promise<AllowedDomain[]>;
303
+ createAllowedDomain(domain: string, defaultRole: RoleLevel): Promise<AllowedDomain>;
304
+ updateAllowedDomain(domain: string, enabled: boolean, defaultRole?: RoleLevel): Promise<void>;
305
+ deleteAllowedDomain(domain: string): Promise<void>;
306
+ }
307
+
308
+ // ============================================================================
309
+ // Email Adapter Interface
310
+ // ============================================================================
311
+
312
+ export interface EmailMessage {
313
+ to: string;
314
+ subject: string;
315
+ text: string;
316
+ html?: string;
317
+ }
318
+
319
+ export interface EmailAdapter {
320
+ send(message: EmailMessage): Promise<void>;
321
+ }
322
+
323
+ // ============================================================================
324
+ // Auth Errors
325
+ // ============================================================================
326
+
327
+ export class AuthError extends Error {
328
+ constructor(
329
+ public code: AuthErrorCode,
330
+ message?: string,
331
+ ) {
332
+ super(message ?? code);
333
+ this.name = "AuthError";
334
+ }
335
+ }
336
+
337
+ export type AuthErrorCode =
338
+ | "invalid_credentials"
339
+ | "invalid_token"
340
+ | "token_expired"
341
+ | "user_not_found"
342
+ | "user_exists"
343
+ | "credential_exists"
344
+ | "max_credentials"
345
+ | "email_not_verified"
346
+ | "signup_not_allowed"
347
+ | "domain_not_allowed"
348
+ | "forbidden"
349
+ | "unauthorized"
350
+ | "rate_limited"
351
+ | "invalid_request"
352
+ | "internal_error";