@artatol-acp/auth-nextjs 0.3.8 → 0.4.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/README.md +259 -474
- package/dist/client/index.d.ts +17 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +64 -32
- package/dist/client/index.js.map +1 -1
- package/dist/handlers/index.d.ts +88 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +359 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/server/index.d.ts +27 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +143 -32
- package/dist/server/index.js.map +1 -1
- package/package.json +6 -2
package/dist/client/index.d.ts
CHANGED
|
@@ -3,17 +3,29 @@ import { type ReactNode } from 'react';
|
|
|
3
3
|
export type ACPAuthContextValue = {
|
|
4
4
|
user: User | null;
|
|
5
5
|
isLoading: boolean;
|
|
6
|
-
login: (email: string, password: string) => Promise<
|
|
6
|
+
login: (email: string, password: string) => Promise<{
|
|
7
|
+
requiresTwoFactor?: boolean;
|
|
8
|
+
tempToken?: string;
|
|
9
|
+
}>;
|
|
10
|
+
verify2FA: (tempToken: string, code: string) => Promise<void>;
|
|
7
11
|
logout: () => Promise<void>;
|
|
8
12
|
refresh: () => Promise<boolean>;
|
|
9
13
|
resendVerification: (email: string) => Promise<void>;
|
|
10
14
|
};
|
|
11
15
|
export type ACPAuthProviderProps = {
|
|
12
16
|
children: ReactNode;
|
|
13
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Base path for auth API routes in your app.
|
|
19
|
+
* @default "/api/auth"
|
|
20
|
+
*/
|
|
21
|
+
apiBasePath?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Initial user data from server-side rendering.
|
|
24
|
+
* If provided, skips initial client-side session fetch.
|
|
25
|
+
*/
|
|
26
|
+
initialUser?: User | null;
|
|
14
27
|
};
|
|
15
|
-
export declare function ACPAuthProvider({ children,
|
|
28
|
+
export declare function ACPAuthProvider({ children, apiBasePath, initialUser, }: ACPAuthProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
16
29
|
export declare function useAuth(): ACPAuthContextValue;
|
|
17
|
-
export {
|
|
18
|
-
export type * from '@artatol-acp/auth-js';
|
|
30
|
+
export type { User } from '@artatol-acp/auth-js';
|
|
19
31
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAuE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5G,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzG,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC;AAIF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CAC3B,CAAC;AA6BF,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,WAAyB,EACzB,WAAW,GACZ,EAAE,oBAAoB,2CAmHtB;AAED,wBAAgB,OAAO,IAAI,mBAAmB,CAM7C;AAGD,YAAY,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/client/index.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
2
|
'use client';
|
|
3
3
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
-
import { ACPAuthClient } from '@artatol-acp/auth-js';
|
|
5
4
|
import { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
|
|
6
5
|
const ACPAuthContext = createContext(null);
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
})
|
|
6
|
+
async function authFetch(path, options = {}) {
|
|
7
|
+
const response = await fetch(path, {
|
|
8
|
+
...options,
|
|
9
|
+
headers: {
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
...options.headers,
|
|
12
|
+
},
|
|
13
|
+
credentials: 'include',
|
|
14
|
+
});
|
|
15
|
+
const data = await response.json();
|
|
16
|
+
if (!data.success) {
|
|
17
|
+
throw new Error(data.error.message);
|
|
18
|
+
}
|
|
19
|
+
return data.data;
|
|
20
|
+
}
|
|
21
|
+
export function ACPAuthProvider({ children, apiBasePath = '/api/auth', initialUser, }) {
|
|
22
|
+
const [user, setUser] = useState(initialUser ?? null);
|
|
23
|
+
const [isLoading, setIsLoading] = useState(initialUser === undefined);
|
|
16
24
|
const intervalRef = useRef(null);
|
|
25
|
+
const apiBase = apiBasePath.replace(/\/$/, '');
|
|
17
26
|
const clearRefreshInterval = useCallback(() => {
|
|
18
27
|
if (intervalRef.current) {
|
|
19
28
|
clearInterval(intervalRef.current);
|
|
@@ -22,43 +31,60 @@ export function ACPAuthProvider({ children, baseUrl }) {
|
|
|
22
31
|
}, []);
|
|
23
32
|
const refresh = useCallback(async () => {
|
|
24
33
|
try {
|
|
25
|
-
await
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
const result = await authFetch(`${apiBase}/session`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
});
|
|
37
|
+
setUser(result.user);
|
|
28
38
|
return true;
|
|
29
39
|
}
|
|
30
40
|
catch {
|
|
41
|
+
setUser(null);
|
|
31
42
|
return false;
|
|
32
43
|
}
|
|
33
|
-
}, []);
|
|
44
|
+
}, [apiBase]);
|
|
34
45
|
const login = useCallback(async (email, password) => {
|
|
35
|
-
const result = await
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
const result = await authFetch(`${apiBase}/login`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: JSON.stringify({ email, password }),
|
|
49
|
+
});
|
|
50
|
+
if (result.requiresTwoFactor) {
|
|
51
|
+
return { requiresTwoFactor: true, tempToken: result.tempToken };
|
|
52
|
+
}
|
|
53
|
+
if (result.user) {
|
|
54
|
+
setUser(result.user);
|
|
38
55
|
}
|
|
39
|
-
|
|
56
|
+
return {};
|
|
57
|
+
}, [apiBase]);
|
|
58
|
+
const verify2FA = useCallback(async (tempToken, code) => {
|
|
59
|
+
const result = await authFetch(`${apiBase}/verify-2fa`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
body: JSON.stringify({ tempToken, code }),
|
|
62
|
+
});
|
|
40
63
|
setUser(result.user);
|
|
41
|
-
}, []);
|
|
64
|
+
}, [apiBase]);
|
|
42
65
|
const logout = useCallback(async () => {
|
|
43
|
-
// First clear the interval to prevent race conditions
|
|
44
66
|
clearRefreshInterval();
|
|
45
|
-
// Then clear user state
|
|
46
67
|
setUser(null);
|
|
47
|
-
// Finally call logout API
|
|
48
68
|
try {
|
|
49
|
-
await
|
|
69
|
+
await authFetch(`${apiBase}/logout`, { method: 'POST' });
|
|
50
70
|
}
|
|
51
71
|
catch {
|
|
52
|
-
// Ignore logout errors
|
|
72
|
+
// Ignore logout errors
|
|
53
73
|
}
|
|
54
|
-
}, [clearRefreshInterval]);
|
|
74
|
+
}, [apiBase, clearRefreshInterval]);
|
|
55
75
|
const resendVerification = useCallback(async (email) => {
|
|
56
|
-
await
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
await authFetch(`${apiBase}/resend-verification`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: JSON.stringify({ email }),
|
|
79
|
+
});
|
|
80
|
+
}, [apiBase]);
|
|
81
|
+
// Initial session restore (skip if initialUser was provided)
|
|
59
82
|
useEffect(() => {
|
|
83
|
+
if (initialUser !== undefined) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
60
86
|
refresh().finally(() => setIsLoading(false));
|
|
61
|
-
}, [refresh]);
|
|
87
|
+
}, [refresh, initialUser]);
|
|
62
88
|
// Auto-refresh interval
|
|
63
89
|
useEffect(() => {
|
|
64
90
|
if (!user) {
|
|
@@ -76,7 +102,15 @@ export function ACPAuthProvider({ children, baseUrl }) {
|
|
|
76
102
|
}, REFRESH_INTERVAL);
|
|
77
103
|
return clearRefreshInterval;
|
|
78
104
|
}, [user, refresh, clearRefreshInterval]);
|
|
79
|
-
return (_jsx(ACPAuthContext.Provider, { value: {
|
|
105
|
+
return (_jsx(ACPAuthContext.Provider, { value: {
|
|
106
|
+
user,
|
|
107
|
+
isLoading,
|
|
108
|
+
login,
|
|
109
|
+
verify2FA,
|
|
110
|
+
logout,
|
|
111
|
+
refresh,
|
|
112
|
+
resendVerification,
|
|
113
|
+
}, children: children }));
|
|
80
114
|
}
|
|
81
115
|
export function useAuth() {
|
|
82
116
|
const context = useContext(ACPAuthContext);
|
|
@@ -85,6 +119,4 @@ export function useAuth() {
|
|
|
85
119
|
}
|
|
86
120
|
return context;
|
|
87
121
|
}
|
|
88
|
-
// Re-export client for direct use
|
|
89
|
-
export { ACPAuthClient } from '@artatol-acp/auth-js';
|
|
90
122
|
//# sourceMappingURL=index.js.map
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,YAAY,CAAC;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { ACPAuthClient, type User, type FetchFunction } from '@artatol-acp/auth-js';
|
|
3
|
+
export type ACPAuthHandlerOptions = {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Cookie configuration
|
|
8
|
+
*/
|
|
9
|
+
cookies?: {
|
|
10
|
+
/**
|
|
11
|
+
* Cookie path
|
|
12
|
+
* @default "/"
|
|
13
|
+
*/
|
|
14
|
+
path?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to use secure cookies (HTTPS only)
|
|
17
|
+
* @default process.env.NODE_ENV === "production"
|
|
18
|
+
*/
|
|
19
|
+
secure?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* SameSite cookie attribute
|
|
22
|
+
* @default "lax"
|
|
23
|
+
*/
|
|
24
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
type ApiSuccess<T> = {
|
|
28
|
+
success: true;
|
|
29
|
+
data: T;
|
|
30
|
+
};
|
|
31
|
+
type ApiError = {
|
|
32
|
+
success: false;
|
|
33
|
+
error: {
|
|
34
|
+
message: string;
|
|
35
|
+
code?: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export declare function createAuthHandlers(options: ACPAuthHandlerOptions): {
|
|
39
|
+
authHandler: (request: NextRequest, context: {
|
|
40
|
+
params: Promise<{
|
|
41
|
+
action: string;
|
|
42
|
+
}>;
|
|
43
|
+
}) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
44
|
+
requiresTwoFactor: boolean;
|
|
45
|
+
tempToken: string;
|
|
46
|
+
}>> | NextResponse<ApiSuccess<{
|
|
47
|
+
user: User;
|
|
48
|
+
}>> | NextResponse<ApiSuccess<{
|
|
49
|
+
message: string;
|
|
50
|
+
}>>>;
|
|
51
|
+
loginHandler: (request: NextRequest) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
52
|
+
requiresTwoFactor: boolean;
|
|
53
|
+
tempToken: string;
|
|
54
|
+
}>> | NextResponse<ApiSuccess<{
|
|
55
|
+
user: User;
|
|
56
|
+
}>>>;
|
|
57
|
+
verify2FAHandler: (request: NextRequest) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
58
|
+
user: User;
|
|
59
|
+
}>>>;
|
|
60
|
+
logoutHandler: (_request: NextRequest) => Promise<NextResponse<ApiSuccess<{
|
|
61
|
+
message: string;
|
|
62
|
+
}>>>;
|
|
63
|
+
sessionHandler: (_request: NextRequest) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
64
|
+
user: User;
|
|
65
|
+
}>>>;
|
|
66
|
+
registerHandler: (request: NextRequest) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
67
|
+
user: User;
|
|
68
|
+
}>>>;
|
|
69
|
+
resendVerificationHandler: (request: NextRequest) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
70
|
+
message: string;
|
|
71
|
+
}>>>;
|
|
72
|
+
forgotPasswordHandler: (request: NextRequest) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
73
|
+
message: string;
|
|
74
|
+
}>>>;
|
|
75
|
+
resetPasswordHandler: (request: NextRequest) => Promise<NextResponse<ApiError> | NextResponse<ApiSuccess<{
|
|
76
|
+
message: string;
|
|
77
|
+
}>>>;
|
|
78
|
+
setAuthCookies: (accessToken: string, refreshToken?: string) => Promise<void>;
|
|
79
|
+
clearAuthCookies: () => Promise<void>;
|
|
80
|
+
getStoredTokens: () => Promise<{
|
|
81
|
+
accessToken: string | undefined;
|
|
82
|
+
refreshToken: string | undefined;
|
|
83
|
+
}>;
|
|
84
|
+
createClient: (customFetch?: FetchFunction) => ACPAuthClient;
|
|
85
|
+
};
|
|
86
|
+
export type AuthHandlers = ReturnType<typeof createAuthHandlers>;
|
|
87
|
+
export {};
|
|
88
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,aAAa,EAAgB,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAElG,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,OAAO,CAAC,EAAE;QACR;;;WAGG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;QACd;;;WAGG;QACH,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;KACtC,CAAC;CACH,CAAC;AAEF,KAAK,UAAU,CAAC,CAAC,IAAI;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC;AAChD,KAAK,QAAQ,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAc9E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,qBAAqB;2BAwWpD,WAAW,WACX;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE;;;;;;;;4BApQb,WAAW;;;;;;gCAsCP,WAAW;;;8BA8Bb,WAAW;;;+BAqCV,WAAW;;;+BAsDX,WAAW;;;yCAyBD,WAAW;;;qCAyBf,WAAW;;;oCAyBZ,WAAW;;;kCAlRb,MAAM,iBAAiB,MAAM;;;;;;iCAhDpC,aAAa;EAwYlD;AAGD,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { cookies } from 'next/headers';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { ACPAuthClient, ACPAuthError } from '@artatol-acp/auth-js';
|
|
4
|
+
function jsonResponse(data, status = 200) {
|
|
5
|
+
return NextResponse.json({ success: true, data }, { status });
|
|
6
|
+
}
|
|
7
|
+
function errorResponse(message, status = 400, code) {
|
|
8
|
+
return NextResponse.json({ success: false, error: { message, code } }, { status });
|
|
9
|
+
}
|
|
10
|
+
export function createAuthHandlers(options) {
|
|
11
|
+
const { baseUrl, apiKey = '', cookies: cookieConfig = {}, } = options;
|
|
12
|
+
const { path: cookiePath = '/', secure = process.env.NODE_ENV === 'production', sameSite = 'lax', } = cookieConfig;
|
|
13
|
+
function createClient(customFetch) {
|
|
14
|
+
return new ACPAuthClient({
|
|
15
|
+
baseUrl,
|
|
16
|
+
apiKey,
|
|
17
|
+
fetch: customFetch,
|
|
18
|
+
autoRefresh: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Extract refresh_token from Set-Cookie header
|
|
23
|
+
*/
|
|
24
|
+
function extractRefreshTokenFromCookies(response) {
|
|
25
|
+
const setCookieHeader = response.headers.get('set-cookie');
|
|
26
|
+
if (!setCookieHeader)
|
|
27
|
+
return null;
|
|
28
|
+
// Parse refresh_token from Set-Cookie header
|
|
29
|
+
const match = setCookieHeader.match(/refresh_token=([^;]+)/);
|
|
30
|
+
return match ? match[1] : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Custom fetch that captures the refresh token from Set-Cookie header
|
|
34
|
+
*/
|
|
35
|
+
function createCapturingFetch() {
|
|
36
|
+
let capturedRefreshToken = null;
|
|
37
|
+
const capturingFetch = async (url, init) => {
|
|
38
|
+
const response = await fetch(url, init);
|
|
39
|
+
// Try to extract refresh token from response
|
|
40
|
+
const refreshToken = extractRefreshTokenFromCookies(response);
|
|
41
|
+
if (refreshToken) {
|
|
42
|
+
capturedRefreshToken = refreshToken;
|
|
43
|
+
}
|
|
44
|
+
return response;
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
fetch: capturingFetch,
|
|
48
|
+
getRefreshToken: () => capturedRefreshToken,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function setAuthCookies(accessToken, refreshToken) {
|
|
52
|
+
const cookieStore = await cookies();
|
|
53
|
+
cookieStore.set('access_token', accessToken, {
|
|
54
|
+
httpOnly: true,
|
|
55
|
+
secure,
|
|
56
|
+
sameSite,
|
|
57
|
+
path: cookiePath,
|
|
58
|
+
maxAge: 60 * 5, // 5 minutes
|
|
59
|
+
});
|
|
60
|
+
if (refreshToken) {
|
|
61
|
+
cookieStore.set('refresh_token', refreshToken, {
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
secure,
|
|
64
|
+
sameSite,
|
|
65
|
+
path: cookiePath,
|
|
66
|
+
maxAge: 60 * 60 * 24 * 30, // 30 days
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function clearAuthCookies() {
|
|
71
|
+
const cookieStore = await cookies();
|
|
72
|
+
cookieStore.delete('access_token');
|
|
73
|
+
cookieStore.delete('refresh_token');
|
|
74
|
+
}
|
|
75
|
+
async function getStoredTokens() {
|
|
76
|
+
const cookieStore = await cookies();
|
|
77
|
+
return {
|
|
78
|
+
accessToken: cookieStore.get('access_token')?.value,
|
|
79
|
+
refreshToken: cookieStore.get('refresh_token')?.value,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* POST /api/auth/login
|
|
84
|
+
* Body: { email: string, password: string }
|
|
85
|
+
*/
|
|
86
|
+
async function loginHandler(request) {
|
|
87
|
+
try {
|
|
88
|
+
const body = await request.json();
|
|
89
|
+
const { email, password } = body;
|
|
90
|
+
if (!email || !password) {
|
|
91
|
+
return errorResponse('Email and password are required', 400);
|
|
92
|
+
}
|
|
93
|
+
// Use capturing fetch to extract refresh_token from Set-Cookie header
|
|
94
|
+
const { fetch: capturingFetch, getRefreshToken } = createCapturingFetch();
|
|
95
|
+
const client = createClient(capturingFetch);
|
|
96
|
+
const result = await client.login({ email, password });
|
|
97
|
+
if ('requiresTwoFactor' in result) {
|
|
98
|
+
return jsonResponse({
|
|
99
|
+
requiresTwoFactor: true,
|
|
100
|
+
tempToken: result.tempToken,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Get refresh token from Set-Cookie header (auth server doesn't return it in JSON body)
|
|
104
|
+
const refreshToken = getRefreshToken();
|
|
105
|
+
await setAuthCookies(result.accessToken, refreshToken ?? undefined);
|
|
106
|
+
return jsonResponse({ user: result.user });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (error instanceof ACPAuthError) {
|
|
110
|
+
return errorResponse(error.message, error.statusCode, error.code);
|
|
111
|
+
}
|
|
112
|
+
return errorResponse('Login failed', 500);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* POST /api/auth/verify-2fa
|
|
117
|
+
* Body: { tempToken: string, code: string }
|
|
118
|
+
*/
|
|
119
|
+
async function verify2FAHandler(request) {
|
|
120
|
+
try {
|
|
121
|
+
const body = await request.json();
|
|
122
|
+
const { tempToken, code } = body;
|
|
123
|
+
if (!tempToken || !code) {
|
|
124
|
+
return errorResponse('Temp token and code are required', 400);
|
|
125
|
+
}
|
|
126
|
+
// Use capturing fetch to extract refresh_token from Set-Cookie header
|
|
127
|
+
const { fetch: capturingFetch, getRefreshToken } = createCapturingFetch();
|
|
128
|
+
const client = createClient(capturingFetch);
|
|
129
|
+
const result = await client.verify2FALogin({ tempToken, code });
|
|
130
|
+
// Get refresh token from Set-Cookie header
|
|
131
|
+
const refreshToken = getRefreshToken();
|
|
132
|
+
await setAuthCookies(result.accessToken, refreshToken ?? undefined);
|
|
133
|
+
return jsonResponse({ user: result.user });
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (error instanceof ACPAuthError) {
|
|
137
|
+
return errorResponse(error.message, error.statusCode, error.code);
|
|
138
|
+
}
|
|
139
|
+
return errorResponse('2FA verification failed', 500);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* POST /api/auth/logout
|
|
144
|
+
*/
|
|
145
|
+
async function logoutHandler(_request) {
|
|
146
|
+
try {
|
|
147
|
+
const { refreshToken } = await getStoredTokens();
|
|
148
|
+
if (refreshToken) {
|
|
149
|
+
// Create a custom fetch that includes the refresh token cookie
|
|
150
|
+
const customFetch = async (url, init) => {
|
|
151
|
+
return fetch(url, {
|
|
152
|
+
...init,
|
|
153
|
+
headers: {
|
|
154
|
+
...init?.headers,
|
|
155
|
+
Cookie: `refresh_token=${refreshToken}`,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
const client = createClient(customFetch);
|
|
160
|
+
try {
|
|
161
|
+
await client.logout();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Ignore logout API errors
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await clearAuthCookies();
|
|
168
|
+
return jsonResponse({ message: 'Logged out' });
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
await clearAuthCookies();
|
|
172
|
+
return jsonResponse({ message: 'Logged out' });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* POST /api/auth/session
|
|
177
|
+
* Refreshes the session and returns current user
|
|
178
|
+
*/
|
|
179
|
+
async function sessionHandler(_request) {
|
|
180
|
+
try {
|
|
181
|
+
const { accessToken, refreshToken } = await getStoredTokens();
|
|
182
|
+
if (!accessToken && !refreshToken) {
|
|
183
|
+
return errorResponse('No session', 401);
|
|
184
|
+
}
|
|
185
|
+
const customFetch = async (url, init) => {
|
|
186
|
+
return fetch(url, {
|
|
187
|
+
...init,
|
|
188
|
+
headers: {
|
|
189
|
+
...init?.headers,
|
|
190
|
+
Cookie: `refresh_token=${refreshToken}`,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
const client = createClient(customFetch);
|
|
195
|
+
// Try to refresh the token
|
|
196
|
+
try {
|
|
197
|
+
const refreshResult = await client.refresh();
|
|
198
|
+
await setAuthCookies(refreshResult.accessToken);
|
|
199
|
+
// Get user data with new token
|
|
200
|
+
const user = await client.me(refreshResult.accessToken);
|
|
201
|
+
return jsonResponse({ user });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Refresh failed, try to use existing access token
|
|
205
|
+
if (accessToken) {
|
|
206
|
+
try {
|
|
207
|
+
const user = await client.me(accessToken);
|
|
208
|
+
return jsonResponse({ user });
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
await clearAuthCookies();
|
|
212
|
+
return errorResponse('Session expired', 401);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
await clearAuthCookies();
|
|
216
|
+
return errorResponse('Session expired', 401);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (error instanceof ACPAuthError) {
|
|
221
|
+
return errorResponse(error.message, error.statusCode, error.code);
|
|
222
|
+
}
|
|
223
|
+
return errorResponse('Session check failed', 500);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* POST /api/auth/register
|
|
228
|
+
* Body: { email: string, password: string }
|
|
229
|
+
*/
|
|
230
|
+
async function registerHandler(request) {
|
|
231
|
+
try {
|
|
232
|
+
const body = await request.json();
|
|
233
|
+
const { email, password } = body;
|
|
234
|
+
if (!email || !password) {
|
|
235
|
+
return errorResponse('Email and password are required', 400);
|
|
236
|
+
}
|
|
237
|
+
const client = createClient();
|
|
238
|
+
const user = await client.register({ email, password });
|
|
239
|
+
return jsonResponse({ user });
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error instanceof ACPAuthError) {
|
|
243
|
+
return errorResponse(error.message, error.statusCode, error.code);
|
|
244
|
+
}
|
|
245
|
+
return errorResponse('Registration failed', 500);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* POST /api/auth/resend-verification
|
|
250
|
+
* Body: { email: string }
|
|
251
|
+
*/
|
|
252
|
+
async function resendVerificationHandler(request) {
|
|
253
|
+
try {
|
|
254
|
+
const body = await request.json();
|
|
255
|
+
const { email } = body;
|
|
256
|
+
if (!email) {
|
|
257
|
+
return errorResponse('Email is required', 400);
|
|
258
|
+
}
|
|
259
|
+
const client = createClient();
|
|
260
|
+
await client.resendVerificationEmail({ email });
|
|
261
|
+
return jsonResponse({ message: 'Verification email sent' });
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
if (error instanceof ACPAuthError) {
|
|
265
|
+
return errorResponse(error.message, error.statusCode, error.code);
|
|
266
|
+
}
|
|
267
|
+
return errorResponse('Failed to send verification email', 500);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* POST /api/auth/forgot-password
|
|
272
|
+
* Body: { email: string }
|
|
273
|
+
*/
|
|
274
|
+
async function forgotPasswordHandler(request) {
|
|
275
|
+
try {
|
|
276
|
+
const body = await request.json();
|
|
277
|
+
const { email } = body;
|
|
278
|
+
if (!email) {
|
|
279
|
+
return errorResponse('Email is required', 400);
|
|
280
|
+
}
|
|
281
|
+
const client = createClient();
|
|
282
|
+
await client.forgotPassword({ email });
|
|
283
|
+
return jsonResponse({ message: 'Password reset email sent' });
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (error instanceof ACPAuthError) {
|
|
287
|
+
return errorResponse(error.message, error.statusCode, error.code);
|
|
288
|
+
}
|
|
289
|
+
return errorResponse('Failed to send reset email', 500);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* POST /api/auth/reset-password
|
|
294
|
+
* Body: { token: string, newPassword: string }
|
|
295
|
+
*/
|
|
296
|
+
async function resetPasswordHandler(request) {
|
|
297
|
+
try {
|
|
298
|
+
const body = await request.json();
|
|
299
|
+
const { token, newPassword } = body;
|
|
300
|
+
if (!token || !newPassword) {
|
|
301
|
+
return errorResponse('Token and new password are required', 400);
|
|
302
|
+
}
|
|
303
|
+
const client = createClient();
|
|
304
|
+
await client.resetPassword({ token, newPassword });
|
|
305
|
+
return jsonResponse({ message: 'Password reset successful' });
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (error instanceof ACPAuthError) {
|
|
309
|
+
return errorResponse(error.message, error.statusCode, error.code);
|
|
310
|
+
}
|
|
311
|
+
return errorResponse('Password reset failed', 500);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Main route handler that dispatches to individual handlers
|
|
316
|
+
*/
|
|
317
|
+
async function authHandler(request, context) {
|
|
318
|
+
const { action } = await context.params;
|
|
319
|
+
switch (action) {
|
|
320
|
+
case 'login':
|
|
321
|
+
return loginHandler(request);
|
|
322
|
+
case 'verify-2fa':
|
|
323
|
+
return verify2FAHandler(request);
|
|
324
|
+
case 'logout':
|
|
325
|
+
return logoutHandler(request);
|
|
326
|
+
case 'session':
|
|
327
|
+
return sessionHandler(request);
|
|
328
|
+
case 'register':
|
|
329
|
+
return registerHandler(request);
|
|
330
|
+
case 'resend-verification':
|
|
331
|
+
return resendVerificationHandler(request);
|
|
332
|
+
case 'forgot-password':
|
|
333
|
+
return forgotPasswordHandler(request);
|
|
334
|
+
case 'reset-password':
|
|
335
|
+
return resetPasswordHandler(request);
|
|
336
|
+
default:
|
|
337
|
+
return errorResponse('Not found', 404);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
// Main combined handler
|
|
342
|
+
authHandler,
|
|
343
|
+
// Individual handlers for custom routing
|
|
344
|
+
loginHandler,
|
|
345
|
+
verify2FAHandler,
|
|
346
|
+
logoutHandler,
|
|
347
|
+
sessionHandler,
|
|
348
|
+
registerHandler,
|
|
349
|
+
resendVerificationHandler,
|
|
350
|
+
forgotPasswordHandler,
|
|
351
|
+
resetPasswordHandler,
|
|
352
|
+
// Utility functions
|
|
353
|
+
setAuthCookies,
|
|
354
|
+
clearAuthCookies,
|
|
355
|
+
getStoredTokens,
|
|
356
|
+
createClient,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=index.js.map
|