@fonderie/auth 1.3.0 → 1.3.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.
- package/brain/outcomes.md +137 -0
- package/brain/signatures.md +139 -0
- package/package.json +5 -4
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/auth — outcomes
|
|
4
|
+
|
|
5
|
+
What this package does to a running app: tables its migrations create,
|
|
6
|
+
rows it seeds, routes it registers. Generated from the migration SQL and
|
|
7
|
+
route tables in source — trust this file instead of reading `dist/` or
|
|
8
|
+
downloading tarballs.
|
|
9
|
+
|
|
10
|
+
## Database tables (after all migrations)
|
|
11
|
+
|
|
12
|
+
### `fonderie_email_verifications`
|
|
13
|
+
|
|
14
|
+
```sql
|
|
15
|
+
token TEXT PRIMARY KEY
|
|
16
|
+
user_id UUID NOT NULL REFERENCES fonderie_users(id) ON DELETE CASCADE
|
|
17
|
+
expires_at TIMESTAMPTZ NOT NULL
|
|
18
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
19
|
+
PRIMARY KEY (user_id)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### `fonderie_mfa_backup_codes`
|
|
23
|
+
|
|
24
|
+
```sql
|
|
25
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
26
|
+
user_id UUID NOT NULL REFERENCES fonderie_users(id) ON DELETE CASCADE
|
|
27
|
+
code_hash TEXT NOT NULL
|
|
28
|
+
used_at TIMESTAMPTZ
|
|
29
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `fonderie_mfa_challenges`
|
|
33
|
+
|
|
34
|
+
```sql
|
|
35
|
+
token TEXT PRIMARY KEY
|
|
36
|
+
user_id UUID NOT NULL REFERENCES fonderie_users(id) ON DELETE CASCADE
|
|
37
|
+
expires_at TIMESTAMPTZ NOT NULL
|
|
38
|
+
used_at TIMESTAMPTZ
|
|
39
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
40
|
+
-- INDEX idx_fonderie_mfa_challenges_expires_at (expires_at)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### `fonderie_password_resets`
|
|
44
|
+
|
|
45
|
+
```sql
|
|
46
|
+
user_id UUID PRIMARY KEY REFERENCES fonderie_users(id) ON DELETE CASCADE
|
|
47
|
+
expires_at TIMESTAMPTZ NOT NULL
|
|
48
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
49
|
+
pin TEXT NOT NULL UNIQUE
|
|
50
|
+
-- INDEX idx_fonderie_password_resets_pin (pin)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `fonderie_phone_verifications`
|
|
54
|
+
|
|
55
|
+
```sql
|
|
56
|
+
phone TEXT PRIMARY KEY
|
|
57
|
+
otp TEXT NOT NULL
|
|
58
|
+
expires_at TIMESTAMPTZ NOT NULL
|
|
59
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
60
|
+
user_id UUID REFERENCES fonderie_users(id) ON DELETE CASCADE
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `fonderie_sessions`
|
|
64
|
+
|
|
65
|
+
```sql
|
|
66
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
67
|
+
user_id UUID NOT NULL REFERENCES fonderie_users(id) ON DELETE CASCADE
|
|
68
|
+
token TEXT NOT NULL UNIQUE
|
|
69
|
+
user_agent TEXT
|
|
70
|
+
ip_address TEXT
|
|
71
|
+
expires_at TIMESTAMPTZ NOT NULL
|
|
72
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
73
|
+
sid UUID
|
|
74
|
+
-- INDEX idx_fonderie_sessions_expires_at (expires_at)
|
|
75
|
+
-- INDEX idx_fonderie_sessions_sid (sid)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `fonderie_users`
|
|
79
|
+
|
|
80
|
+
```sql
|
|
81
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
82
|
+
email TEXT UNIQUE
|
|
83
|
+
password_hash TEXT
|
|
84
|
+
first_name TEXT
|
|
85
|
+
last_name TEXT
|
|
86
|
+
phone TEXT
|
|
87
|
+
profile_image_url TEXT
|
|
88
|
+
locale TEXT NOT NULL DEFAULT 'en-US'
|
|
89
|
+
timezone TEXT NOT NULL DEFAULT 'UTC'
|
|
90
|
+
provider TEXT
|
|
91
|
+
provider_id TEXT
|
|
92
|
+
is_active BOOLEAN NOT NULL DEFAULT true
|
|
93
|
+
last_login TIMESTAMPTZ
|
|
94
|
+
preferences JSONB NOT NULL DEFAULT '{"notifications":{"email":true
|
|
95
|
+
suspended BOOLEAN NOT NULL DEFAULT false
|
|
96
|
+
whitelist BOOLEAN NOT NULL DEFAULT false
|
|
97
|
+
ip_whitelist JSONB NOT NULL DEFAULT '[]'
|
|
98
|
+
mfa_enabled BOOLEAN NOT NULL DEFAULT false
|
|
99
|
+
mfa_secret TEXT
|
|
100
|
+
email_verified_at TIMESTAMPTZ
|
|
101
|
+
deleted_at TIMESTAMPTZ
|
|
102
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
103
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
104
|
+
IF NOT EXISTS phone_verified_at TIMESTAMPTZ
|
|
105
|
+
CONSTRAINT fonderie_users_phone_unique UNIQUE (phone)
|
|
106
|
+
mfa_secret_pending TEXT
|
|
107
|
+
mfa_secret_pending_expires_at TIMESTAMPTZ
|
|
108
|
+
-- INDEX idx_fonderie_users_email (email)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Raw SQL ships in `node_modules/@fonderie/auth/dist/migrations/sql/` — read it there if you must; never download tarballs.
|
|
112
|
+
|
|
113
|
+
## HTTP routes registered
|
|
114
|
+
|
|
115
|
+
| Method | Path | Middleware chain (auth / validation / handler) |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| POST | `/auth/email/forgot` | `ipLimit('forgot') → validate(forgotPasswordSchema) → acctLimit('forgot') → auth.forgotPassword` |
|
|
118
|
+
| POST | `/auth/email/reset` | `validate(resetPasswordSchema) → auth.resetPassword` |
|
|
119
|
+
| GET | `/auth/google` | `oauth.googleInit` |
|
|
120
|
+
| GET | `/auth/google/callback` | `oauth.googleCallback` |
|
|
121
|
+
| POST | `/auth/login` | `ipLimit('login') → validate(loginSchema) → acctLimit('login') → auth.login` |
|
|
122
|
+
| POST | `/auth/logout` | `requireAuth → validate(refreshSchema) → auth.logout` |
|
|
123
|
+
| POST | `/auth/mfa/backup-codes` | `requireAuth → requireEmailLogin → requireVerified → validate(mfaTokenSchema) → mfa.regenerateBackupCodes` |
|
|
124
|
+
| POST | `/auth/mfa/disable` | `requireAuth → requireEmailLogin → requireVerified → validate(mfaTokenSchema) → mfa.disable` |
|
|
125
|
+
| POST | `/auth/mfa/setup` | `requireAuth → requireEmailLogin → requireVerified → mfa.setup` |
|
|
126
|
+
| POST | `/auth/mfa/verify` | `ipLimit('mfaVerify') → requireAnyAuth → requireEmailLogin → requireVerified → validate(mfaTokenSchema) → mfa.verify` |
|
|
127
|
+
| POST | `/auth/refresh` | `validate(refreshSchema) → auth.refresh` |
|
|
128
|
+
| POST | `/auth/register` | `ipLimit('register') → validate(registerSchema) → auth.register` |
|
|
129
|
+
| GET | `/auth/send-verification` | `requireAuth → auth.sendVerification` |
|
|
130
|
+
| POST | `/auth/verify` | `requireAuth → validate(verifySchema) → auth.verify` |
|
|
131
|
+
| DELETE | `/users` | `requireAuth → verifyGate → user.deleteMe` |
|
|
132
|
+
| GET | `/users` | `requireAuth → user.me` |
|
|
133
|
+
| PUT | `/users/email` | `requireAuth → verifyGate → validate(updateEmailSchema) → user.updateEmail` |
|
|
134
|
+
| PUT | `/users/password` | `requireAuth → validate(changePasswordSchema) → user.changePassword` |
|
|
135
|
+
| PUT | `/users/phone` | `requireAuth → verifyGate → validate(updatePhoneSchema) → user.updatePhone` |
|
|
136
|
+
| PUT | `/users/preferences` | `requireAuth → verifyGate → validate(updatePreferencesSchema) → user.updatePreferences` |
|
|
137
|
+
| PUT | `/users/profile` | `requireAuth → verifyGate → validate(updateProfileSchema) → user.updateProfile` |
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/auth — signatures
|
|
4
|
+
|
|
5
|
+
## @fonderie/auth
|
|
6
|
+
|
|
7
|
+
Subpath exports: `@fonderie/auth/types`, `@fonderie/auth/middleware`, `@fonderie/auth/migrations`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
interface IUser {
|
|
11
|
+
id: string;
|
|
12
|
+
email: string | null;
|
|
13
|
+
firstName: string | null;
|
|
14
|
+
lastName: string | null;
|
|
15
|
+
phone: string | null;
|
|
16
|
+
profileImageUrl: string | null;
|
|
17
|
+
locale: string;
|
|
18
|
+
timezone: string;
|
|
19
|
+
isActive: boolean;
|
|
20
|
+
lastLogin: Date | null;
|
|
21
|
+
preferences: IUserPreferences;
|
|
22
|
+
suspended: boolean;
|
|
23
|
+
whitelist: boolean;
|
|
24
|
+
ipWhitelist: string[];
|
|
25
|
+
deletedAt: Date | null;
|
|
26
|
+
createdAt: Date;
|
|
27
|
+
updatedAt: Date;
|
|
28
|
+
mfaEnabled: boolean;
|
|
29
|
+
passwordHash: string | null;
|
|
30
|
+
emailVerifiedAt: Date | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ISession {
|
|
34
|
+
id: string;
|
|
35
|
+
token: string;
|
|
36
|
+
userId: string;
|
|
37
|
+
userAgent: string | null;
|
|
38
|
+
ipAddress: string | null;
|
|
39
|
+
expiresAt: Date;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface IMfaChallenge {
|
|
44
|
+
token: string;
|
|
45
|
+
userId: string;
|
|
46
|
+
expiresAt: Date;
|
|
47
|
+
usedAt: Date | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
new AuthModule(store: IStoreAdapter, config: IAuthConfig, bus?: EventBus | undefined): AuthModule
|
|
51
|
+
.name: "@fonderie/auth"
|
|
52
|
+
.install(app: IFonderieApp): void
|
|
53
|
+
|
|
54
|
+
interface IAuthConfig extends IAuthSecrets, IAuthRuntimeConfig {
|
|
55
|
+
secureCookies?: boolean;
|
|
56
|
+
rateLimit?: IAuthRateLimitConfig | false;
|
|
57
|
+
accessTokenDuration?: string;
|
|
58
|
+
providers: ('email' | 'phone' | 'google' | 'github')[];
|
|
59
|
+
appName?: string;
|
|
60
|
+
resolve?: (ctx: {
|
|
61
|
+
meta: Record<string, unknown>;
|
|
62
|
+
}) => Partial<IAuthRuntimeConfig>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface IAuthSecrets {
|
|
66
|
+
jwtSecret: string;
|
|
67
|
+
google?: {
|
|
68
|
+
clientId: string;
|
|
69
|
+
clientSecret: string;
|
|
70
|
+
redirectUri: string;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface IAuthRuntimeConfig {
|
|
75
|
+
sessionDuration?: string;
|
|
76
|
+
verificationCooldown?: number;
|
|
77
|
+
mfa?: boolean;
|
|
78
|
+
requireVerification?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const AUTH_CONFIG_KEYS: { sessionDuration: string; verificationCooldown: string; mfa: string; requireVerification: string; }
|
|
82
|
+
|
|
83
|
+
const MESSAGE_KEYS: { readonly emailRegistration: "email-registration"; readonly emailVerification: "email-verification"; readonly passwordReset: "password-reset"; readonly phoneOtp: "phone-otp"; readonly mfaEnabled: "mfa-enabled"; readonly mfaDisabled: "mfa-disabled"; readonly mfaBackupCodesRegenerated: "mfa-backup-codes-regenerated"; readonly emailChanged: "email-changed"; readonly phoneChanged: "phone-changed"; }
|
|
84
|
+
|
|
85
|
+
type AuthMessageKey = (typeof MESSAGE_KEYS)[keyof typeof MESSAGE_KEYS];
|
|
86
|
+
|
|
87
|
+
interface IUserDTO {
|
|
88
|
+
id: string;
|
|
89
|
+
email: string;
|
|
90
|
+
firstName: string;
|
|
91
|
+
lastName: string;
|
|
92
|
+
phone: string;
|
|
93
|
+
profileImageUrl: string;
|
|
94
|
+
isActive: boolean;
|
|
95
|
+
lastLogin: string;
|
|
96
|
+
preferences: IUserPreferences;
|
|
97
|
+
isEmailVerified: boolean;
|
|
98
|
+
isPhoneVerified: boolean;
|
|
99
|
+
mfaEnabled: boolean;
|
|
100
|
+
suspended: boolean;
|
|
101
|
+
whitelist: boolean;
|
|
102
|
+
ipWhitelist: string[];
|
|
103
|
+
createdAt: string;
|
|
104
|
+
updatedAt: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toUserDTO(user: IUser, phoneVerified?: boolean): IUserDTO
|
|
108
|
+
|
|
109
|
+
function validate(schema: IRequestSchema): Middleware
|
|
110
|
+
|
|
111
|
+
namespace schemas — exports: ChangePasswordInput, LoginInput, RegisterInput, ResetPasswordInput, changePasswordSchema, forgotPasswordSchema, loginSchema, mfaTokenSchema, refreshSchema, registerSchema, resetPasswordSchema, updateEmailSchema, updatePhoneSchema, updatePreferencesSchema, updateProfileSchema, verifySchema
|
|
112
|
+
|
|
113
|
+
type RegisterInput = z.infer<typeof registerSchema>;
|
|
114
|
+
|
|
115
|
+
type LoginInput = z.infer<typeof loginSchema>;
|
|
116
|
+
|
|
117
|
+
type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
|
|
118
|
+
|
|
119
|
+
type ChangePasswordInput = z.infer<typeof changePasswordSchema>;
|
|
120
|
+
|
|
121
|
+
function withSession(store: IStoreAdapter, config: IAuthConfig): Middleware
|
|
122
|
+
|
|
123
|
+
function requireAuth(ctx: IFonderieContext, next: () => Promise<Response>): Promise<Response>
|
|
124
|
+
|
|
125
|
+
function normalizeEmail(email: string): string
|
|
126
|
+
|
|
127
|
+
function normalizeEmailSafe(email: string): string | null
|
|
128
|
+
|
|
129
|
+
function buildAuthIpLimiter(route: AuthLimitedRoute, store: IStoreAdapter, config: false | IAuthRateLimitConfig | undefined): Middleware | null
|
|
130
|
+
|
|
131
|
+
function buildAuthAccountLimiter(route: AuthLimitedRoute, store: IStoreAdapter, config: false | IAuthRateLimitConfig | undefined): Middleware | null
|
|
132
|
+
|
|
133
|
+
interface IAuthRateLimitConfig {
|
|
134
|
+
store?: IRateLimitStore;
|
|
135
|
+
rules?: Partial<Record<AuthLimitedRoute, IRateLimitRule | false>>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type AuthLimitedRoute = 'login' | 'register' | 'forgot' | 'mfaVerify';
|
|
139
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonderie/auth",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Drop-in auth for SaaS — email/password, phone OTP, Google OAuth, stateless JWT sessions, TOTP-based MFA with backup codes, and self-service password recovery.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fonderie-js",
|
|
@@ -88,16 +88,17 @@
|
|
|
88
88
|
},
|
|
89
89
|
"files": [
|
|
90
90
|
"dist",
|
|
91
|
+
"brain",
|
|
91
92
|
"LICENSE",
|
|
92
93
|
"README.md"
|
|
93
94
|
],
|
|
94
95
|
"repository": {
|
|
95
96
|
"type": "git",
|
|
96
|
-
"url": "git+https://github.com/
|
|
97
|
+
"url": "git+https://github.com/fonderiejs/sdk.git",
|
|
97
98
|
"directory": "packages/auth"
|
|
98
99
|
},
|
|
99
|
-
"homepage": "https://github.com/
|
|
100
|
+
"homepage": "https://github.com/fonderiejs/sdk/tree/main/packages/auth#readme",
|
|
100
101
|
"bugs": {
|
|
101
|
-
"url": "https://github.com/
|
|
102
|
+
"url": "https://github.com/fonderiejs/sdk/issues"
|
|
102
103
|
}
|
|
103
104
|
}
|