@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.
@@ -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: { 'Content-Type': 'application/json' },
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
- window.location.href = callbackUrl || '/';
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);
@@ -44,6 +44,7 @@ export function useAuth() {
44
44
  }
45
45
 
46
46
  */
47
+ //sdk/javascript/src/react/provider.tsx
47
48
  'use client';
48
49
  Object.defineProperty(exports, "__esModule", { value: true });
49
50
  exports.CoreIAMProvider = CoreIAMProvider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreiam/auth",
3
- "version": "1.0.392-beta.1",
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": "coreIAM",
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",