@artatol-acp/auth-nextjs 0.1.0

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 ADDED
@@ -0,0 +1,214 @@
1
+ # @artatol-acp/auth-nextjs
2
+
3
+ Next.js SDK for Artatol Cloud Platform Authentication with support for App Router, Server Actions, and Middleware.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @artatol-acp/auth-nextjs
9
+ # or
10
+ pnpm add @artatol-acp/auth-nextjs
11
+ ```
12
+
13
+ ## Setup
14
+
15
+ ### 1. Initialize Server-side
16
+
17
+ Create `lib/auth.ts`:
18
+
19
+ ```typescript
20
+ import { initACPAuth } from '@artatol-acp/auth-nextjs/server';
21
+ import { readFileSync } from 'fs';
22
+
23
+ const publicKey = readFileSync('./keys/public.pem', 'utf-8');
24
+
25
+ export const auth = initACPAuth({
26
+ baseUrl: process.env.ACP_AUTH_URL || 'https://sso.artatol.com',
27
+ jwtPublicKey: publicKey,
28
+ });
29
+ ```
30
+
31
+ ### 2. Add Middleware (Optional)
32
+
33
+ Create `middleware.ts`:
34
+
35
+ ```typescript
36
+ import { createACPAuthMiddleware } from '@artatol-acp/auth-nextjs/middleware';
37
+ import { readFileSync } from 'fs';
38
+
39
+ const publicKey = readFileSync('./keys/public.pem', 'utf-8');
40
+
41
+ export const middleware = createACPAuthMiddleware({
42
+ jwtPublicKey: publicKey,
43
+ publicPaths: ['/login', '/register', '/forgot-password'],
44
+ loginPath: '/login',
45
+ });
46
+
47
+ export const config = {
48
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
49
+ };
50
+ ```
51
+
52
+ ### 3. Add Client Provider (Optional)
53
+
54
+ In your root layout (`app/layout.tsx`):
55
+
56
+ ```typescript
57
+ import { ACPAuthProvider } from '@artatol-acp/auth-nextjs/client';
58
+
59
+ export default function RootLayout({ children }) {
60
+ return (
61
+ <html>
62
+ <body>
63
+ <ACPAuthProvider baseUrl={process.env.NEXT_PUBLIC_ACP_AUTH_URL}>
64
+ {children}
65
+ </ACPAuthProvider>
66
+ </body>
67
+ </html>
68
+ );
69
+ }
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ ### Server Components
75
+
76
+ ```typescript
77
+ import { getUser } from '@artatol-acp/auth-nextjs/server';
78
+
79
+ export default async function ProfilePage() {
80
+ const user = await getUser();
81
+
82
+ if (!user) {
83
+ return <div>Not logged in</div>;
84
+ }
85
+
86
+ return (
87
+ <div>
88
+ <h1>Welcome {user.email}</h1>
89
+ </div>
90
+ );
91
+ }
92
+ ```
93
+
94
+ ### Server Actions
95
+
96
+ ```typescript
97
+ 'use server';
98
+
99
+ import { login, register, logout } from '@artatol-acp/auth-nextjs/server';
100
+ import { redirect } from 'next/navigation';
101
+
102
+ export async function loginAction(formData: FormData) {
103
+ const email = formData.get('email') as string;
104
+ const password = formData.get('password') as string;
105
+
106
+ const result = await login(email, password);
107
+
108
+ if ('requiresTwoFactor' in result) {
109
+ // Handle 2FA
110
+ return { requires2FA: true, tempToken: result.tempToken };
111
+ }
112
+
113
+ redirect('/dashboard');
114
+ }
115
+
116
+ export async function registerAction(formData: FormData) {
117
+ const email = formData.get('email') as string;
118
+ const password = formData.get('password') as string;
119
+
120
+ await register(email, password);
121
+ redirect('/login');
122
+ }
123
+
124
+ export async function logoutAction() {
125
+ await logout();
126
+ redirect('/login');
127
+ }
128
+ ```
129
+
130
+ ### Client Components
131
+
132
+ ```typescript
133
+ 'use client';
134
+
135
+ import { useAuth } from '@artatol-acp/auth-nextjs/client';
136
+
137
+ export function LoginForm() {
138
+ const { login, user, isLoading } = useAuth();
139
+
140
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
141
+ e.preventDefault();
142
+ const formData = new FormData(e.currentTarget);
143
+
144
+ try {
145
+ await login(
146
+ formData.get('email') as string,
147
+ formData.get('password') as string
148
+ );
149
+ } catch (error) {
150
+ console.error('Login failed:', error);
151
+ }
152
+ };
153
+
154
+ if (isLoading) return <div>Loading...</div>;
155
+ if (user) return <div>Welcome {user.email}</div>;
156
+
157
+ return (
158
+ <form onSubmit={handleSubmit}>
159
+ <input name="email" type="email" required />
160
+ <input name="password" type="password" required />
161
+ <button type="submit">Login</button>
162
+ </form>
163
+ );
164
+ }
165
+ ```
166
+
167
+ ### API Routes
168
+
169
+ ```typescript
170
+ import { ACPAuthClient } from '@artatol-acp/auth-nextjs/server';
171
+
172
+ const client = new ACPAuthClient({
173
+ baseUrl: process.env.ACP_AUTH_URL!,
174
+ });
175
+
176
+ export async function POST(request: Request) {
177
+ const { email, password } = await request.json();
178
+
179
+ const result = await client.login({ email, password });
180
+
181
+ return Response.json(result);
182
+ }
183
+ ```
184
+
185
+ ## Environment Variables
186
+
187
+ ```env
188
+ ACP_AUTH_URL=https://sso.artatol.com
189
+ NEXT_PUBLIC_ACP_AUTH_URL=https://sso.artatol.com
190
+ ```
191
+
192
+ ## API Reference
193
+
194
+ ### Server Functions
195
+
196
+ - `initACPAuth(options)` - Initialize the auth client
197
+ - `getUser()` - Get current user from access token
198
+ - `verifyAccessToken(token)` - Verify and decode access token
199
+ - `refreshAccessToken()` - Refresh access token using refresh token cookie
200
+ - `login(email, password)` - Login user
201
+ - `logout()` - Logout user
202
+ - `register(email, password)` - Register new user
203
+
204
+ ### Client Hooks
205
+
206
+ - `useAuth()` - Access auth context (user, isLoading, login, logout, refresh)
207
+
208
+ ### Middleware
209
+
210
+ - `createACPAuthMiddleware(options)` - Create middleware for route protection
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,17 @@
1
+ import type { User } from '@artatol-acp/auth-js';
2
+ import { type ReactNode } from 'react';
3
+ export type ACPAuthContextValue = {
4
+ user: User | null;
5
+ isLoading: boolean;
6
+ login: (email: string, password: string) => Promise<void>;
7
+ logout: () => Promise<void>;
8
+ refresh: () => Promise<void>;
9
+ };
10
+ export type ACPAuthProviderProps = {
11
+ children: ReactNode;
12
+ baseUrl: string;
13
+ };
14
+ export declare function ACPAuthProvider({ children, baseUrl }: ACPAuthProviderProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function useAuth(): ACPAuthContextValue;
16
+ export { ACPAuthClient } from '@artatol-acp/auth-js';
17
+ export type * from '@artatol-acp/auth-js';
@@ -0,0 +1,49 @@
1
+ /** @jsxImportSource react */
2
+ 'use client';
3
+ import { ACPAuthClient } from '@artatol-acp/auth-js';
4
+ import { createContext, useContext, useState, useEffect } from 'react';
5
+ const ACPAuthContext = createContext(null);
6
+ export function ACPAuthProvider({ children, baseUrl }) {
7
+ const [user, setUser] = useState(null);
8
+ const [isLoading, setIsLoading] = useState(true);
9
+ const [client] = useState(() => new ACPAuthClient({ baseUrl }));
10
+ const refresh = async () => {
11
+ try {
12
+ await client.refresh();
13
+ // After refresh, we need to verify the new token
14
+ // This would require a server action or API route
15
+ // For now, we'll just mark as authenticated
16
+ }
17
+ catch (error) {
18
+ setUser(null);
19
+ }
20
+ };
21
+ const login = async (email, password) => {
22
+ const result = await client.login({ email, password });
23
+ if ('requiresTwoFactor' in result) {
24
+ throw new Error('2FA_REQUIRED');
25
+ }
26
+ // User logged in successfully
27
+ setUser(result.user);
28
+ };
29
+ const logout = async () => {
30
+ await client.logout();
31
+ setUser(null);
32
+ };
33
+ useEffect(() => {
34
+ // Try to restore session on mount
35
+ refresh().finally(() => setIsLoading(false));
36
+ }, []);
37
+ return (<ACPAuthContext.Provider value={{ user, isLoading, login, logout, refresh }}>
38
+ {children}
39
+ </ACPAuthContext.Provider>);
40
+ }
41
+ export function useAuth() {
42
+ const context = useContext(ACPAuthContext);
43
+ if (!context) {
44
+ throw new Error('useAuth must be used within ACPAuthProvider');
45
+ }
46
+ return context;
47
+ }
48
+ // Re-export client for direct use
49
+ export { ACPAuthClient } from '@artatol-acp/auth-js';
@@ -0,0 +1,3 @@
1
+ export * from './server/index.js';
2
+ export * from './client/index.js';
3
+ export * from './middleware.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './server/index.js';
2
+ export * from './client/index.js';
3
+ export * from './middleware.js';
@@ -0,0 +1,7 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ export type ACPAuthMiddlewareOptions = {
3
+ jwtPublicKey: string;
4
+ publicPaths?: string[];
5
+ loginPath?: string;
6
+ };
7
+ export declare function createACPAuthMiddleware(options: ACPAuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
@@ -0,0 +1,40 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { jwtVerify, importSPKI } from 'jose';
3
+ let publicKey = null;
4
+ export function createACPAuthMiddleware(options) {
5
+ const { jwtPublicKey, publicPaths = ['/login', '/register', '/forgot-password', '/reset-password'], loginPath = '/login', } = options;
6
+ return async function acpAuthMiddleware(request) {
7
+ const { pathname } = request.nextUrl;
8
+ // Allow public paths
9
+ if (publicPaths.some(path => pathname.startsWith(path))) {
10
+ return NextResponse.next();
11
+ }
12
+ // Check for access token
13
+ const accessToken = request.cookies.get('access_token')?.value;
14
+ if (!accessToken) {
15
+ // Redirect to login
16
+ const url = request.nextUrl.clone();
17
+ url.pathname = loginPath;
18
+ url.searchParams.set('from', pathname);
19
+ return NextResponse.redirect(url);
20
+ }
21
+ // Verify access token
22
+ try {
23
+ if (!publicKey) {
24
+ publicKey = await importSPKI(jwtPublicKey, 'EdDSA');
25
+ }
26
+ await jwtVerify(accessToken, publicKey, {
27
+ algorithms: ['EdDSA'],
28
+ });
29
+ return NextResponse.next();
30
+ }
31
+ catch (error) {
32
+ // Token invalid, try to refresh
33
+ // For now, redirect to login
34
+ const url = request.nextUrl.clone();
35
+ url.pathname = loginPath;
36
+ url.searchParams.set('from', pathname);
37
+ return NextResponse.redirect(url);
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,14 @@
1
+ import { ACPAuthClient, type User, type LoginResult } from '@artatol-acp/auth-js';
2
+ export type ACPAuthServerOptions = {
3
+ baseUrl: string;
4
+ jwtPublicKey: string;
5
+ };
6
+ export declare function initACPAuth(options: ACPAuthServerOptions): ACPAuthClient;
7
+ export declare function verifyAccessToken(token: string): Promise<User>;
8
+ export declare function getUser(): Promise<User | null>;
9
+ export declare function refreshAccessToken(): Promise<string | null>;
10
+ export declare function login(email: string, password: string): Promise<LoginResult>;
11
+ export declare function logout(): Promise<void>;
12
+ export declare function register(email: string, password: string): Promise<User>;
13
+ export { ACPAuthClient } from '@artatol-acp/auth-js';
14
+ export type * from '@artatol-acp/auth-js';
@@ -0,0 +1,107 @@
1
+ import { cookies } from 'next/headers';
2
+ import { ACPAuthClient } from '@artatol-acp/auth-js';
3
+ import { jwtVerify, importSPKI } from 'jose';
4
+ let authClient = null;
5
+ let publicKey = null;
6
+ let config = null;
7
+ export function initACPAuth(options) {
8
+ config = options;
9
+ authClient = new ACPAuthClient({ baseUrl: options.baseUrl });
10
+ return authClient;
11
+ }
12
+ async function getPublicKey() {
13
+ if (!config) {
14
+ throw new Error('ACP Auth not initialized. Call initACPAuth() first.');
15
+ }
16
+ if (!publicKey) {
17
+ publicKey = await importSPKI(config.jwtPublicKey, 'EdDSA');
18
+ }
19
+ return publicKey;
20
+ }
21
+ function getClient() {
22
+ if (!authClient) {
23
+ throw new Error('ACP Auth not initialized. Call initACPAuth() first.');
24
+ }
25
+ return authClient;
26
+ }
27
+ export async function verifyAccessToken(token) {
28
+ const key = await getPublicKey();
29
+ try {
30
+ const { payload } = await jwtVerify(token, key, {
31
+ algorithms: ['EdDSA'],
32
+ });
33
+ return {
34
+ id: payload.authUserId,
35
+ email: payload.email,
36
+ };
37
+ }
38
+ catch (error) {
39
+ throw new Error('Invalid access token');
40
+ }
41
+ }
42
+ export async function getUser() {
43
+ const cookieStore = await cookies();
44
+ const accessToken = cookieStore.get('access_token')?.value;
45
+ if (!accessToken) {
46
+ return null;
47
+ }
48
+ try {
49
+ return await verifyAccessToken(accessToken);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ export async function refreshAccessToken() {
56
+ const client = getClient();
57
+ try {
58
+ const { accessToken } = await client.refresh();
59
+ // Set new access token cookie
60
+ const cookieStore = await cookies();
61
+ cookieStore.set('access_token', accessToken, {
62
+ httpOnly: true,
63
+ secure: process.env.NODE_ENV === 'production',
64
+ sameSite: 'lax',
65
+ maxAge: 60 * 5, // 5 minutes
66
+ path: '/',
67
+ });
68
+ return accessToken;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ export async function login(email, password) {
75
+ const client = getClient();
76
+ const result = await client.login({ email, password });
77
+ if ('accessToken' in result) {
78
+ // Set access token cookie
79
+ const cookieStore = await cookies();
80
+ cookieStore.set('access_token', result.accessToken, {
81
+ httpOnly: true,
82
+ secure: process.env.NODE_ENV === 'production',
83
+ sameSite: 'lax',
84
+ maxAge: 60 * 5, // 5 minutes
85
+ path: '/',
86
+ });
87
+ }
88
+ return result;
89
+ }
90
+ export async function logout() {
91
+ const client = getClient();
92
+ try {
93
+ await client.logout();
94
+ }
95
+ finally {
96
+ // Clear cookies
97
+ const cookieStore = await cookies();
98
+ cookieStore.delete('access_token');
99
+ cookieStore.delete('refresh_token');
100
+ }
101
+ }
102
+ export async function register(email, password) {
103
+ const client = getClient();
104
+ return await client.register({ email, password });
105
+ }
106
+ // Re-export client for direct use if needed
107
+ export { ACPAuthClient } from '@artatol-acp/auth-js';
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@artatol-acp/auth-nextjs",
3
+ "version": "0.1.0",
4
+ "description": "Next.js SDK for Artatol Cloud Platform Authentication",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./server": {
15
+ "import": "./dist/server/index.js",
16
+ "types": "./dist/server/index.d.ts"
17
+ },
18
+ "./client": {
19
+ "import": "./dist/client/index.js",
20
+ "types": "./dist/client/index.d.ts"
21
+ },
22
+ "./middleware": {
23
+ "import": "./dist/middleware.js",
24
+ "types": "./dist/middleware.d.ts"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "dev": "tsc --watch",
33
+ "typecheck": "tsc --noEmit"
34
+ },
35
+ "keywords": [
36
+ "acp",
37
+ "auth",
38
+ "authentication",
39
+ "artatol",
40
+ "jwt",
41
+ "2fa",
42
+ "nextjs",
43
+ "next.js"
44
+ ],
45
+ "author": "Artatol",
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "@artatol-acp/auth-js": "workspace:*",
49
+ "jose": "^6.1.3"
50
+ },
51
+ "peerDependencies": {
52
+ "next": ">=15.0.0",
53
+ "react": ">=18.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^24.10.1",
57
+ "@types/react": "^18.3.18",
58
+ "next": "^15.1.6",
59
+ "react": "^18.3.1",
60
+ "typescript": "^5.7.2"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ }
65
+ }