@coreiam/auth 1.0.392-beta.1 → 1.0.392-beta.12
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/dist/core/index.d.ts +63 -3
- package/dist/core/index.js +167 -11
- package/dist/express/index.js +8 -8
- package/dist/hono/index.d.ts +33 -0
- package/dist/hono/index.js +195 -0
- package/dist/nextjs/actions.d.ts +12 -3
- package/dist/nextjs/actions.js +130 -29
- package/dist/nextjs/index.d.ts +1 -1
- package/dist/nextjs/index.js +55 -11
- package/dist/nextjs/middleware.js +127 -32
- package/dist/nextjs/provider.d.ts +17 -1
- package/dist/nextjs/provider.js +136 -4
- package/dist/react/provider.js +1 -0
- package/package.json +28 -7
package/dist/nextjs/provider.js
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
// export function useAuth() {
|
|
52
52
|
// return useContext(AuthContext);
|
|
53
53
|
// }
|
|
54
|
-
// src/nextjs/provider.tsx
|
|
54
|
+
// sdk/javascript/src/nextjs/provider.tsx
|
|
55
55
|
'use client';
|
|
56
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
57
|
exports.CoreIAMProvider = CoreIAMProvider;
|
|
@@ -60,6 +60,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
60
60
|
const react_1 = require("react");
|
|
61
61
|
const navigation_1 = require("next/navigation");
|
|
62
62
|
const actions_1 = require("./actions");
|
|
63
|
+
const core_1 = require("../core");
|
|
63
64
|
const AuthContext = (0, react_1.createContext)({
|
|
64
65
|
identity: null,
|
|
65
66
|
isLoading: true,
|
|
@@ -69,6 +70,10 @@ const AuthContext = (0, react_1.createContext)({
|
|
|
69
70
|
register: async () => ({ error: 'Not mounted' }),
|
|
70
71
|
verifyMfa: async () => ({ error: 'Not mounted' }),
|
|
71
72
|
refetchIdentity: async () => { },
|
|
73
|
+
requestOtp: async () => ({ error: 'Not mounted' }),
|
|
74
|
+
verifyOtp: async () => ({ error: 'Not mounted' }),
|
|
75
|
+
forgotPassword: async () => ({ error: 'Not mounted' }),
|
|
76
|
+
resetPassword: async () => ({ error: 'Not mounted' }),
|
|
72
77
|
});
|
|
73
78
|
function CoreIAMProvider({ children, initialIdentity }) {
|
|
74
79
|
const [identity, setIdentity] = (0, react_1.useState)(initialIdentity ?? null);
|
|
@@ -107,9 +112,19 @@ function CoreIAMProvider({ children, initialIdentity }) {
|
|
|
107
112
|
const verifyMfa = async (tempToken, code) => {
|
|
108
113
|
setIsLoading(true);
|
|
109
114
|
try {
|
|
115
|
+
// FIX (SDK-2): perform the CSRF handshake before calling /api/auth/mfa/login.
|
|
116
|
+
// That path is in CSRF_PROTECTED_PATHS and the backend CsrfGuard is
|
|
117
|
+
// double-submit (the x-csrf-token header must equal the csrfToken cookie).
|
|
118
|
+
// The login-phase handshake already placed the csrf cookie in the browser
|
|
119
|
+
// jar, so only the header echo was missing — which made every provider
|
|
120
|
+
// MFA login fail with 403. This now mirrors the login() flow exactly.
|
|
121
|
+
const csrfToken = await getCsrfToken();
|
|
110
122
|
const res = await fetch('/api/auth/mfa/login', {
|
|
111
123
|
method: 'POST',
|
|
112
|
-
headers: {
|
|
124
|
+
headers: {
|
|
125
|
+
'Content-Type': 'application/json',
|
|
126
|
+
'x-csrf-token': csrfToken,
|
|
127
|
+
},
|
|
113
128
|
credentials: 'include',
|
|
114
129
|
body: JSON.stringify({ tempToken, code }),
|
|
115
130
|
});
|
|
@@ -128,6 +143,101 @@ function CoreIAMProvider({ children, initialIdentity }) {
|
|
|
128
143
|
setIsLoading(false);
|
|
129
144
|
}
|
|
130
145
|
};
|
|
146
|
+
const requestOtp = async (email) => {
|
|
147
|
+
setIsLoading(true);
|
|
148
|
+
try {
|
|
149
|
+
const csrfToken = await getCsrfToken();
|
|
150
|
+
const res = await fetch('/api/auth/request-otp', {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
|
|
153
|
+
credentials: 'include',
|
|
154
|
+
body: JSON.stringify({ email }),
|
|
155
|
+
});
|
|
156
|
+
const data = await res.json();
|
|
157
|
+
if (!res.ok)
|
|
158
|
+
return { error: data.message || 'OTP request failed' };
|
|
159
|
+
return { success: true };
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return { error: 'Something went wrong' };
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
setIsLoading(false);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const verifyOtp = async (email, otp) => {
|
|
169
|
+
setIsLoading(true);
|
|
170
|
+
try {
|
|
171
|
+
const csrfToken = await getCsrfToken();
|
|
172
|
+
const res = await fetch('/api/auth/verify-otp', {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
|
|
175
|
+
credentials: 'include',
|
|
176
|
+
body: JSON.stringify({ email, otp }),
|
|
177
|
+
});
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
if (!res.ok)
|
|
180
|
+
return { error: data.message || 'Invalid or expired OTP' };
|
|
181
|
+
// Session established (jwt cookie set server-side) — hard refresh to
|
|
182
|
+
// let the server re-render authenticated, same as login()/verifyMfa().
|
|
183
|
+
window.location.href = '/';
|
|
184
|
+
return { success: true };
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return { error: 'Something went wrong' };
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
setIsLoading(false);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const forgotPassword = async (email) => {
|
|
194
|
+
setIsLoading(true);
|
|
195
|
+
try {
|
|
196
|
+
const csrfToken = await getCsrfToken();
|
|
197
|
+
const res = await fetch('/api/auth/request-reset', {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
|
|
200
|
+
credentials: 'include',
|
|
201
|
+
body: JSON.stringify({ email }),
|
|
202
|
+
});
|
|
203
|
+
const data = await res.json();
|
|
204
|
+
if (!res.ok)
|
|
205
|
+
return { error: data.message || 'Reset request failed' };
|
|
206
|
+
// Backend response is uniform (no account-existence leak) — treat
|
|
207
|
+
// success generically; UI copy ("if the account exists…") is the app's.
|
|
208
|
+
return { success: true };
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return { error: 'Something went wrong' };
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
setIsLoading(false);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const resetPassword = async (email, otp, newPassword) => {
|
|
218
|
+
setIsLoading(true);
|
|
219
|
+
try {
|
|
220
|
+
const csrfToken = await getCsrfToken();
|
|
221
|
+
const res = await fetch('/api/auth/reset-password', {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },
|
|
224
|
+
credentials: 'include',
|
|
225
|
+
body: JSON.stringify({ email, otp, newPassword }),
|
|
226
|
+
});
|
|
227
|
+
const data = await res.json();
|
|
228
|
+
if (!res.ok)
|
|
229
|
+
return { error: data.message || 'Password reset failed' };
|
|
230
|
+
// All sessions revoked server-side — route to login with the new password.
|
|
231
|
+
window.location.href = '/auth/login';
|
|
232
|
+
return { success: true };
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return { error: 'Something went wrong' };
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
setIsLoading(false);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
131
241
|
const login = async (values, callbackUrl) => {
|
|
132
242
|
setIsLoading(true);
|
|
133
243
|
try {
|
|
@@ -151,7 +261,20 @@ function CoreIAMProvider({ children, initialIdentity }) {
|
|
|
151
261
|
return { twoFactor: true, tempToken: data.tempToken };
|
|
152
262
|
}
|
|
153
263
|
// Hard refresh to let server re-render with new cookies
|
|
154
|
-
|
|
264
|
+
// FIX (SDK-8): defense-in-depth — only same-origin relative callback URLs.
|
|
265
|
+
// Airtight defense-in-depth against open redirects
|
|
266
|
+
const cb = callbackUrl || '/';
|
|
267
|
+
let target;
|
|
268
|
+
try {
|
|
269
|
+
target = new URL(cb, window.location.origin);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return { error: 'Invalid callback URL' };
|
|
273
|
+
}
|
|
274
|
+
if (target.origin !== window.location.origin) {
|
|
275
|
+
return { error: 'Invalid callback URL' };
|
|
276
|
+
}
|
|
277
|
+
window.location.href = target.pathname + target.search + target.hash;
|
|
155
278
|
return { success: true };
|
|
156
279
|
}
|
|
157
280
|
catch {
|
|
@@ -192,6 +315,15 @@ function CoreIAMProvider({ children, initialIdentity }) {
|
|
|
192
315
|
if (!res.ok) {
|
|
193
316
|
if (data.code === 'OAUTH_ACCOUNT_EXISTS') {
|
|
194
317
|
// Automatically redirect to the OAuth provider!
|
|
318
|
+
// FIX (SDK-8): route only to known providers (local literal — importing
|
|
319
|
+
// OAUTH_PROVIDERS from ../core would pull server code into the client bundle).
|
|
320
|
+
if (!['google', 'github'].includes(data.provider)) {
|
|
321
|
+
return { error: 'Unsupported provider' };
|
|
322
|
+
}
|
|
323
|
+
// Use the exported Set from core instead of a hardcoded array to prevent drift
|
|
324
|
+
if (!core_1.OAUTH_PROVIDERS.has(data.provider)) {
|
|
325
|
+
return { error: 'Unsupported provider' };
|
|
326
|
+
}
|
|
195
327
|
window.location.href = `/api/auth/oauth/${data.provider}`;
|
|
196
328
|
return { success: true };
|
|
197
329
|
}
|
|
@@ -207,7 +339,7 @@ function CoreIAMProvider({ children, initialIdentity }) {
|
|
|
207
339
|
setIsLoading(false);
|
|
208
340
|
}
|
|
209
341
|
};
|
|
210
|
-
return ((0, jsx_runtime_1.jsx)(AuthContext.Provider, { value: { identity, isLoading, isAuthenticated: !!identity, login, logout, register, verifyMfa, refetchIdentity }, children: children }));
|
|
342
|
+
return ((0, jsx_runtime_1.jsx)(AuthContext.Provider, { value: { identity, isLoading, isAuthenticated: !!identity, login, logout, register, verifyMfa, refetchIdentity, requestOtp, verifyOtp, forgotPassword, resetPassword }, children: children }));
|
|
211
343
|
}
|
|
212
344
|
function useAuth() {
|
|
213
345
|
return (0, react_1.useContext)(AuthContext);
|
package/dist/react/provider.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreiam/auth",
|
|
3
|
-
"version": "1.0.392-beta.
|
|
3
|
+
"version": "1.0.392-beta.12",
|
|
4
4
|
"main": "./dist/core/index.js",
|
|
5
5
|
"types": "./dist/core/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
"types": "./dist/express/index.d.ts",
|
|
24
24
|
"import": "./dist/express/index.js",
|
|
25
25
|
"require": "./dist/express/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./hono": {
|
|
28
|
+
"types": "./dist/hono/index.d.ts",
|
|
29
|
+
"import": "./dist/hono/index.js",
|
|
30
|
+
"require": "./dist/hono/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./ui": {
|
|
33
|
+
"types": "./dist/ui/buttons.d.ts",
|
|
34
|
+
"import": "./dist/ui/buttons.js",
|
|
35
|
+
"require": "./dist/ui/buttons.js"
|
|
26
36
|
}
|
|
27
37
|
},
|
|
28
38
|
"files": [
|
|
@@ -30,24 +40,31 @@
|
|
|
30
40
|
],
|
|
31
41
|
"scripts": {
|
|
32
42
|
"build": "tsc",
|
|
33
|
-
"test": "vitest run"
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"prepublishOnly": "grep -q dev.e-qalam.com dist/core/index.js && grep -q api.e-qalam.com dist/core/index.js || { echo \"STALE DIST: rebuild before publish (beta.11 lesson)\"; exit 1; }"
|
|
34
45
|
},
|
|
35
46
|
"keywords": [
|
|
36
47
|
"coreiam",
|
|
37
48
|
"auth",
|
|
38
49
|
"saas",
|
|
39
50
|
"multi-tenant",
|
|
40
|
-
"nextjs"
|
|
51
|
+
"nextjs",
|
|
52
|
+
"hono",
|
|
53
|
+
"react",
|
|
54
|
+
"vite",
|
|
55
|
+
"express"
|
|
41
56
|
],
|
|
42
|
-
"author": "
|
|
57
|
+
"author": "coreiam",
|
|
43
58
|
"license": "MIT",
|
|
44
59
|
"publishConfig": {
|
|
45
|
-
"access": "public"
|
|
60
|
+
"access": "public",
|
|
61
|
+
"tag": "beta"
|
|
46
62
|
},
|
|
47
63
|
"peerDependencies": {
|
|
48
64
|
"express": "^4.0.0 || ^5.0.0",
|
|
49
65
|
"next": ">=14",
|
|
50
|
-
"react": ">=18"
|
|
66
|
+
"react": ">=18",
|
|
67
|
+
"hono": ">=4"
|
|
51
68
|
},
|
|
52
69
|
"peerDependenciesMeta": {
|
|
53
70
|
"next": {
|
|
@@ -58,6 +75,9 @@
|
|
|
58
75
|
},
|
|
59
76
|
"express": {
|
|
60
77
|
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"hono": {
|
|
80
|
+
"optional": true
|
|
61
81
|
}
|
|
62
82
|
},
|
|
63
83
|
"devDependencies": {
|
|
@@ -69,7 +89,8 @@
|
|
|
69
89
|
"react": "^19.2.7",
|
|
70
90
|
"supertest": "^7.3.0",
|
|
71
91
|
"typescript": "^5.0.0",
|
|
72
|
-
"vitest": "^3.2.7"
|
|
92
|
+
"vitest": "^3.2.7",
|
|
93
|
+
"hono": "^4.6.0"
|
|
73
94
|
},
|
|
74
95
|
"dependencies": {
|
|
75
96
|
"@radix-ui/react-slot": "^1.3.3",
|