@auditauth/next 0.1.9 → 0.2.0-beta.2

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 CHANGED
@@ -1,3 +1,134 @@
1
1
  # @auditauth/next
2
2
 
3
- Official AuditAuth SDK for Next.js (App Router).
3
+ `@auditauth/next` is the AuditAuth integration for Next.js App Router. It
4
+ provides route handlers, auth middleware, protected server handlers, session
5
+ helpers, and authenticated fetch wrappers.
6
+
7
+ ## Install
8
+
9
+ Install the package in your Next.js application.
10
+
11
+ ```bash
12
+ npm install @auditauth/next
13
+ ```
14
+
15
+ ## Create the AuditAuth provider
16
+
17
+ Create one shared instance with `createAuditAuthNext()`.
18
+
19
+ ```ts
20
+ // src/providers/auth.ts
21
+ import { createAuditAuthNext } from '@auditauth/next'
22
+
23
+ export const auditauth = createAuditAuthNext({
24
+ apiKey: process.env.AUDITAUTH_API_KEY!,
25
+ appId: process.env.AUDITAUTH_APP_ID!,
26
+ baseUrl: 'http://localhost:3000',
27
+ redirectUrl: 'http://localhost:3000/private',
28
+ })
29
+ ```
30
+
31
+ ## Register auth API handlers
32
+
33
+ Expose the SDK handlers through a catch-all route.
34
+
35
+ ```ts
36
+ // src/app/api/auditauth/[...auditauth]/route.ts
37
+ import { auditauth } from '@/providers/auth'
38
+
39
+ export const { GET, POST } = auditauth.handlers
40
+ ```
41
+
42
+ The handler covers these endpoints:
43
+
44
+ - `GET /api/auditauth/login`
45
+ - `GET /api/auditauth/callback`
46
+ - `GET /api/auditauth/logout`
47
+ - `GET /api/auditauth/portal`
48
+ - `GET /api/auditauth/session`
49
+ - `GET /api/auditauth/refresh`
50
+ - `POST /api/auditauth/metrics`
51
+
52
+ ## Protect private routes with middleware
53
+
54
+ Call `auditauth.middleware()` on route groups that require authentication.
55
+
56
+ ```ts
57
+ // src/proxy.ts
58
+ import { NextResponse, type NextRequest } from 'next/server'
59
+ import { auditauth } from '@/providers/auth'
60
+
61
+ export async function proxy(request: NextRequest) {
62
+ if (request.nextUrl.pathname.startsWith('/private')) {
63
+ return auditauth.middleware(request)
64
+ }
65
+
66
+ return NextResponse.next()
67
+ }
68
+ ```
69
+
70
+ ## Access session data in server components
71
+
72
+ Use `auditauth.getSession()` in server components, route handlers, or server
73
+ actions.
74
+
75
+ ```ts
76
+ const session = await auditauth.getSession()
77
+ if (!session) return null
78
+ ```
79
+
80
+ ## Protect custom route handlers
81
+
82
+ Wrap handlers with `auditauth.withAuthRequest()` to enforce token validation and
83
+ inject the verified token payload.
84
+
85
+ ```ts
86
+ import { auditauth } from '@/providers/auth'
87
+ import { NextResponse } from 'next/server'
88
+
89
+ export const GET = auditauth.withAuthRequest(async (_req, _ctx, session) => {
90
+ return NextResponse.json({ email: session.email })
91
+ })
92
+ ```
93
+
94
+ ## Call protected APIs from the server
95
+
96
+ Use `auditauth.fetch()` for server-side requests with automatic refresh and
97
+ request metrics.
98
+
99
+ ```ts
100
+ const response = await auditauth.fetch('https://api.example.com/private')
101
+ ```
102
+
103
+ ## Client helpers
104
+
105
+ For client-side navigation shortcuts, import these helpers:
106
+
107
+ - `login()`
108
+ - `logout()`
109
+ - `goToPortal()`
110
+
111
+ You can also use `AuditAuthGuard` and `useAuditAuth` to gate client-rendered
112
+ UI sections.
113
+
114
+ ## Compatibility
115
+
116
+ This package requires:
117
+
118
+ - Node.js `>=18.18.0`
119
+ - Next.js `>=13`
120
+ - React `>=18`
121
+ - React DOM `>=18`
122
+
123
+ ## Resources
124
+
125
+ - Repository: https://github.com/nimibyte/auditauth-sdk
126
+ - Documentation: https://docs.auditauth.com
127
+
128
+ ## Example
129
+
130
+ See `examples/next` for a complete App Router integration.
131
+
132
+ ## License
133
+
134
+ MIT
@@ -0,0 +1,29 @@
1
+ import type { NextRequest } from 'next/server.js';
2
+ import { NextResponse } from 'next/server.js';
3
+ import type { AuditAuthConfig, SessionUser } from '@auditauth/core';
4
+ import type { AuditAuthTokenPayload } from '@auditauth/node';
5
+ type AuditAuthNextFacade = {
6
+ handlers: {
7
+ GET: (req: NextRequest, ctx: {
8
+ params: Promise<{
9
+ auditauth: string[];
10
+ }>;
11
+ }) => Promise<Response>;
12
+ POST: (req: NextRequest, ctx: {
13
+ params: Promise<{
14
+ auditauth: string[];
15
+ }>;
16
+ }) => Promise<Response>;
17
+ };
18
+ middleware: (req: NextRequest) => Promise<NextResponse>;
19
+ getSession: () => Promise<SessionUser | null>;
20
+ hasSession: () => Promise<boolean>;
21
+ fetch: (url: string, init?: RequestInit) => Promise<Response>;
22
+ getLoginUrl: () => URL;
23
+ getLogoutUrl: () => URL;
24
+ getPortalUrl: () => URL;
25
+ withAuthRequest: <C>(handler: (req: NextRequest, ctx: C, session: AuditAuthTokenPayload) => Promise<Response>) => (req: NextRequest, ctx: C) => Promise<Response>;
26
+ };
27
+ declare function createAuditAuthNext(config: AuditAuthConfig): AuditAuthNextFacade;
28
+ export type { AuditAuthNextFacade };
29
+ export { createAuditAuthNext };
@@ -0,0 +1,52 @@
1
+ import { cookies } from 'next/headers.js';
2
+ import { AuditAuthNext } from '../sdk.js';
3
+ import { SETTINGS } from '../settings.js';
4
+ function createAuditAuthNext(config) {
5
+ const base = new URL(config.baseUrl);
6
+ async function createInstance() {
7
+ const store = await cookies();
8
+ return new AuditAuthNext(config, {
9
+ get: (name) => store.get(name)?.value,
10
+ set: (name, value, options) => store.set(name, value, options),
11
+ remove: (name) => store.delete(name),
12
+ });
13
+ }
14
+ return {
15
+ handlers: {
16
+ GET: async (req, ctx) => {
17
+ const instance = await createInstance();
18
+ return instance.getHandlers().GET(req, ctx);
19
+ },
20
+ POST: async (req, ctx) => {
21
+ const instance = await createInstance();
22
+ return instance.getHandlers().POST(req, ctx);
23
+ },
24
+ },
25
+ middleware: async (req) => {
26
+ const instance = await createInstance();
27
+ return instance.middleware(req);
28
+ },
29
+ getSession: async () => {
30
+ const instance = await createInstance();
31
+ return instance.getSession();
32
+ },
33
+ hasSession: async () => {
34
+ const instance = await createInstance();
35
+ return instance.hasSession();
36
+ },
37
+ fetch: async (url, init) => {
38
+ const instance = await createInstance();
39
+ return instance.fetch(url, init);
40
+ },
41
+ withAuthRequest: (handler) => {
42
+ return async (req, ctx) => {
43
+ const instance = await createInstance();
44
+ return instance.withAuthRequest(handler)(req, ctx);
45
+ };
46
+ },
47
+ getLoginUrl: () => new URL(SETTINGS.bff.paths.login, base),
48
+ getLogoutUrl: () => new URL(SETTINGS.bff.paths.logout, base),
49
+ getPortalUrl: () => new URL(SETTINGS.bff.paths.portal, base),
50
+ };
51
+ }
52
+ export { createAuditAuthNext };
@@ -0,0 +1 @@
1
+ export * from './createAuditAuth.js';
@@ -0,0 +1 @@
1
+ export * from './createAuditAuth.js';
package/dist/guard.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SessionUser } from "./types";
1
+ import { SessionUser } from "@auditauth/core";
2
2
  type AuthContextValue = {
3
3
  user: SessionUser;
4
4
  };
package/dist/guard.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext, useEffect, useMemo, useState } from "react";
4
- import { SETTINGS } from "./settings";
4
+ import { SETTINGS } from './settings.js';
5
5
  const AuthContext = createContext(null);
6
6
  const useAuditAuth = () => {
7
7
  const ctx = useContext(AuthContext);
package/dist/helpers.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* -------------------------------------------------------------------------- */
2
2
  /* CLIENT SHORTCUTS */
3
3
  /* -------------------------------------------------------------------------- */
4
- import { SETTINGS } from "./settings";
4
+ import { SETTINGS } from './settings.js';
5
5
  const login = () => {
6
6
  window.location.href = SETTINGS.bff.paths.login;
7
7
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { AuditAuthNext } from './sdk';
2
- export { AuditAuthGuard, useAuditAuth } from './guard';
3
- export { login, logout, goToPortal } from './helpers';
4
- export { auditauthFetch } from './request';
5
- export type * from './types';
1
+ export { AuditAuthNext } from './sdk.js';
2
+ export { AuditAuthGuard, useAuditAuth } from './guard.js';
3
+ export { login, logout, goToPortal } from './helpers.js';
4
+ export { auditauthFetch } from './request.js';
5
+ export type * from './types.js';
6
+ export * from './facade/index.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export { AuditAuthNext } from './sdk';
2
- export { AuditAuthGuard, useAuditAuth } from './guard';
3
- export { login, logout, goToPortal } from './helpers';
4
- export { auditauthFetch } from './request';
1
+ export { AuditAuthNext } from './sdk.js';
2
+ export { AuditAuthGuard, useAuditAuth } from './guard.js';
3
+ export { login, logout, goToPortal } from './helpers.js';
4
+ export { auditauthFetch } from './request.js';
5
+ export * from './facade/index.js';
package/dist/request.js CHANGED
@@ -1,10 +1,25 @@
1
1
  'use server';
2
- import { cookies } from "next/headers";
3
- import { SETTINGS } from "./settings";
2
+ import { cookies } from 'next/headers.js';
3
+ import { SETTINGS } from './settings.js';
4
+ import { headers } from 'next/headers.js';
5
+ import { refreshTokens } from "@auditauth/core";
6
+ const getRequestOrigin = async () => {
7
+ const h = await headers();
8
+ const proto = h.get('x-forwarded-proto') ??
9
+ (process.env.NODE_ENV === 'production' ? 'https' : 'http');
10
+ const host = h.get('x-forwarded-host') ??
11
+ h.get('host');
12
+ if (!host) {
13
+ throw new Error('Cannot resolve request origin');
14
+ }
15
+ return `${proto}://${host}`;
16
+ };
4
17
  const auditauthFetch = async (url, init = {}) => {
5
18
  const cookieManager = await cookies();
6
- const access_token = cookieManager.get(SETTINGS.cookies.access.name);
7
- const refresh_token = cookieManager.get(SETTINGS.cookies.refresh.name);
19
+ const origin = await getRequestOrigin();
20
+ const access_token = cookieManager.get(SETTINGS.storage_keys.access)?.value;
21
+ const refresh_token = cookieManager.get(SETTINGS.storage_keys.refresh)?.value;
22
+ const session_id = cookieManager.get(SETTINGS.storage_keys.session_id)?.value;
8
23
  const doFetch = (token) => fetch(url, {
9
24
  ...init,
10
25
  headers: {
@@ -13,21 +28,14 @@ const auditauthFetch = async (url, init = {}) => {
13
28
  },
14
29
  });
15
30
  const start = performance.now();
16
- let response = await doFetch(access_token?.value);
31
+ let response = await doFetch(access_token);
17
32
  if (response.status === 401 && refresh_token) {
18
- const refreshResponse = await fetch(`${SETTINGS.domains.api}/auth/refresh`, {
19
- method: 'POST',
20
- headers: { 'Content-Type': 'application/json' },
21
- body: JSON.stringify({
22
- refresh_token,
23
- client_type: 'server',
24
- }),
25
- });
26
- if (!refreshResponse.ok)
33
+ const refreshData = await refreshTokens({ refresh_token, client_type: 'server' });
34
+ if (!refreshData) {
27
35
  return response;
28
- const data = await refreshResponse.json();
29
- if (data?.access_token && data?.refresh_token) {
30
- response = await doFetch(data.access_token);
36
+ }
37
+ if (refreshData.access_token && refreshData.refresh_token) {
38
+ response = await doFetch(refreshData.access_token);
31
39
  }
32
40
  }
33
41
  queueMicrotask(() => {
@@ -42,10 +50,10 @@ const auditauthFetch = async (url, init = {}) => {
42
50
  duration_ms: Math.round(performance.now() - start),
43
51
  },
44
52
  };
45
- fetch(`${SETTINGS.bff.paths.metrics}`, {
53
+ fetch(`${origin}${SETTINGS.bff.paths.metrics}`, {
46
54
  method: 'POST',
47
55
  headers: { 'Content-Type': 'application/json' },
48
- body: JSON.stringify({ ...payload }),
56
+ body: JSON.stringify({ ...payload, session_id }),
49
57
  }).catch(() => { });
50
58
  });
51
59
  return response;
package/dist/sdk.d.ts CHANGED
@@ -1,36 +1,24 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
- import { AuditAuthConfig, CookieAdapter, Metric, SessionUser } from "./types";
1
+ import { NextRequest, NextResponse } from 'next/server.js';
2
+ import type { CookieAdapter } from './types.js';
3
+ import { AuditAuthConfig, SessionUser } from '@auditauth/core';
4
+ import { AuditAuthTokenPayload } from "@auditauth/node";
3
5
  declare class AuditAuthNext {
4
6
  private config;
5
7
  private cookies;
6
8
  constructor(config: AuditAuthConfig, cookies: CookieAdapter);
7
- verifyAccessToken(token: string): Promise<boolean>;
8
9
  private getCookieTokens;
9
10
  private setCookieTokens;
11
+ private pushMetric;
10
12
  getSession(): SessionUser | null;
11
13
  hasSession(): boolean;
12
- private buildAuthUrl;
13
- callback(request: NextRequest): Promise<{
14
- ok: boolean;
15
- url: string;
16
- }>;
17
- logout(): Promise<void>;
18
- getPortalUrl(): Promise<{
19
- ok: boolean;
20
- url: null;
21
- reason: string;
22
- } | {
23
- ok: boolean;
24
- url: string;
25
- reason: null;
26
- }>;
27
- private refreshRequest;
28
- request(url: string, init?: RequestInit): Promise<Response>;
29
- metrics(payload: Metric): Promise<Response>;
30
- private pushMetric;
31
- refresh(): Promise<{
32
- ok: boolean;
33
- }>;
14
+ getLoginUrl(): URL;
15
+ getLogoutUrl(): URL;
16
+ getPortalUrl(): URL;
17
+ private callback;
18
+ private logout;
19
+ withAuthRequest<C>(handler: (req: NextRequest, ctx: C, session: AuditAuthTokenPayload) => Promise<Response>): (req: NextRequest, ctx: C) => Promise<Response>;
20
+ fetch(url: string, init?: RequestInit): Promise<Response>;
21
+ private refresh;
34
22
  middleware(request: NextRequest): Promise<NextResponse<unknown>>;
35
23
  getHandlers(): {
36
24
  GET: (req: NextRequest, ctx: {
@@ -38,7 +26,7 @@ declare class AuditAuthNext {
38
26
  auditauth: string[];
39
27
  }>;
40
28
  }) => Promise<Response>;
41
- POST: (req: Request, ctx: {
29
+ POST: (req: NextRequest, ctx: {
42
30
  params: Promise<{
43
31
  auditauth: string[];
44
32
  }>;
package/dist/sdk.js CHANGED
@@ -1,14 +1,8 @@
1
1
  'use server';
2
- import { NextResponse } from "next/server";
3
- import { importSPKI, jwtVerify } from 'jose';
4
- import { SETTINGS } from "./settings";
5
- /* -------------------------------------------------------------------------- */
6
- /* KEYS */
7
- /* -------------------------------------------------------------------------- */
8
- let cachedKey = null;
9
- /* -------------------------------------------------------------------------- */
10
- /* MAIN CLASS */
11
- /* -------------------------------------------------------------------------- */
2
+ import { NextResponse } from 'next/server.js';
3
+ import { SETTINGS } from './settings.js';
4
+ import { buildAuthUrl, authorizeCode, revokeSession, buildPortalUrl, refreshTokens, sendMetrics, } from '@auditauth/core';
5
+ import { verifyRequest } from "@auditauth/node";
12
6
  class AuditAuthNext {
13
7
  constructor(config, cookies) {
14
8
  if (!config.appId)
@@ -25,40 +19,22 @@ class AuditAuthNext {
25
19
  this.config = config;
26
20
  this.cookies = cookies;
27
21
  }
28
- /* ------------------------------------------------------------------------ */
29
- /* AUTH PRIMITIVES */
30
- /* ------------------------------------------------------------------------ */
31
- async verifyAccessToken(token) {
32
- try {
33
- cachedKey =
34
- cachedKey ||
35
- await importSPKI(SETTINGS.jwt_public_key, 'RS256');
36
- await jwtVerify(token, cachedKey, {
37
- issuer: SETTINGS.jwt_issuer,
38
- audience: this.config.appId,
39
- });
40
- return true;
41
- }
42
- catch {
43
- return false;
44
- }
45
- }
46
22
  getCookieTokens() {
47
23
  return {
48
- access: this.cookies.get(SETTINGS.cookies.access.name),
49
- refresh: this.cookies.get(SETTINGS.cookies.refresh.name),
24
+ access: this.cookies.get(SETTINGS.storage_keys.access),
25
+ refresh: this.cookies.get(SETTINGS.storage_keys.refresh),
50
26
  };
51
27
  }
52
28
  setCookieTokens(params) {
53
29
  const isSecure = this.config.redirectUrl.includes('https');
54
- this.cookies.set(SETTINGS.cookies.access.name, params.access_token, {
30
+ this.cookies.set(SETTINGS.storage_keys.access, params.access_token, {
55
31
  httpOnly: true,
56
32
  sameSite: 'lax',
57
33
  secure: isSecure,
58
34
  path: '/',
59
35
  maxAge: params.access_expires_seconds - 60,
60
36
  });
61
- this.cookies.set(SETTINGS.cookies.refresh.name, params.refresh_token, {
37
+ this.cookies.set(SETTINGS.storage_keys.refresh, params.refresh_token, {
62
38
  httpOnly: true,
63
39
  sameSite: 'lax',
64
40
  secure: isSecure,
@@ -66,135 +42,91 @@ class AuditAuthNext {
66
42
  maxAge: params.refresh_expires_seconds - 60,
67
43
  });
68
44
  }
69
- /* ------------------------------------------------------------------------ */
70
- /* SESSION HELPERS */
71
- /* ------------------------------------------------------------------------ */
45
+ pushMetric(payload) {
46
+ const session_id = this.cookies.get(SETTINGS.storage_keys.session_id);
47
+ queueMicrotask(() => {
48
+ fetch(`${this.config.baseUrl}${SETTINGS.bff.paths.metrics}`, {
49
+ method: 'POST',
50
+ headers: { 'Content-Type': 'application/json' },
51
+ body: JSON.stringify({ ...payload, session_id }),
52
+ }).catch(() => { });
53
+ });
54
+ }
72
55
  getSession() {
73
- return JSON.parse(this.cookies.get(SETTINGS.cookies.session.name) || '{}')?.user || null;
56
+ return JSON.parse(this.cookies.get(SETTINGS.storage_keys.session) || '{}')?.user || null;
74
57
  }
75
58
  hasSession() {
76
- return !!this.cookies.get(SETTINGS.cookies.session.name);
59
+ return !!this.cookies.get(SETTINGS.storage_keys.session);
77
60
  }
78
- /* ------------------------------------------------------------------------ */
79
- /* AUTH FLOWS */
80
- /* ------------------------------------------------------------------------ */
81
- async buildAuthUrl() {
82
- const response = await fetch(`${SETTINGS.domains.api}/apps/login`, {
83
- method: 'POST',
84
- headers: { 'x-api-key': this.config.apiKey },
85
- });
86
- if (!response.ok) {
87
- throw new Error('invalid_app');
88
- }
89
- const { code, redirectUrl } = await response.json();
90
- const url = new URL(redirectUrl);
91
- url.searchParams.set('code', code);
61
+ getLoginUrl() {
62
+ const url = new URL(SETTINGS.bff.paths.login, this.config.baseUrl);
63
+ return url;
64
+ }
65
+ getLogoutUrl() {
66
+ const url = new URL(SETTINGS.bff.paths.logout, this.config.baseUrl);
67
+ return url;
68
+ }
69
+ getPortalUrl() {
70
+ const url = new URL(SETTINGS.bff.paths.portal, this.config.baseUrl);
92
71
  return url;
93
72
  }
94
73
  async callback(request) {
95
74
  const code = new URL(request.url).searchParams.get('code');
96
- if (!code) {
97
- return {
98
- ok: false,
99
- url: `${SETTINGS.domains.client}/auth/invalid?reason=wrong_config`,
75
+ try {
76
+ const { data } = await authorizeCode({ code, client_type: 'server' });
77
+ const session = {
78
+ user: data.user,
100
79
  };
80
+ const isSecure = this.config.redirectUrl.includes('http');
81
+ this.cookies.set(SETTINGS.storage_keys.session, JSON.stringify(session), {
82
+ httpOnly: true,
83
+ sameSite: 'lax',
84
+ secure: isSecure,
85
+ path: '/',
86
+ maxAge: data.refresh_expires_seconds - 60,
87
+ });
88
+ this.setCookieTokens({
89
+ access_token: data.access_token,
90
+ access_expires_seconds: data.access_expires_seconds,
91
+ refresh_token: data.refresh_token,
92
+ refresh_expires_seconds: data.refresh_expires_seconds,
93
+ });
94
+ return { ok: true, url: this.config.redirectUrl };
101
95
  }
102
- const response = await fetch(`${SETTINGS.domains.api}/auth/authorize`, {
103
- method: 'POST',
104
- headers: { 'Content-Type': 'application/json' },
105
- body: JSON.stringify({ code, client_type: 'server' }),
106
- });
107
- if (!response.ok) {
96
+ catch {
108
97
  return {
109
98
  ok: false,
110
- url: `${SETTINGS.domains.client}/auth/invalid?reason=unauthorized`,
99
+ url: `${SETTINGS.domains.client}/auth/invalid?reason=wrong_config`,
111
100
  };
112
101
  }
113
- const result = await response.json();
114
- const session = {
115
- user: {
116
- _id: result.user._id.toString(),
117
- email: result.user.email,
118
- avatar: result.user.avatar,
119
- name: result.user.name,
120
- },
121
- };
122
- const isSecure = this.config.redirectUrl.includes('http');
123
- this.cookies.set(SETTINGS.cookies.session.name, JSON.stringify(session), {
124
- httpOnly: true,
125
- sameSite: 'lax',
126
- secure: isSecure,
127
- path: '/',
128
- maxAge: result.refresh_expires_seconds - 60,
129
- });
130
- this.setCookieTokens({
131
- access_token: result.access_token,
132
- access_expires_seconds: result.access_expires_seconds,
133
- refresh_token: result.refresh_token,
134
- refresh_expires_seconds: result.refresh_expires_seconds,
135
- });
136
- return { ok: true, url: this.config.redirectUrl };
137
102
  }
138
103
  async logout() {
139
104
  const { access } = this.getCookieTokens();
140
- if (access) {
141
- await fetch(`${SETTINGS.domains.api}/auth/revoke`, {
142
- method: 'PATCH',
143
- headers: { Authorization: `Bearer ${access}` },
144
- }).catch(() => { });
145
- }
146
- this.cookies.remove(SETTINGS.cookies.access.name);
147
- this.cookies.remove(SETTINGS.cookies.refresh.name);
148
- this.cookies.remove(SETTINGS.cookies.session.name);
105
+ this.cookies.remove(SETTINGS.storage_keys.access);
106
+ this.cookies.remove(SETTINGS.storage_keys.refresh);
107
+ this.cookies.remove(SETTINGS.storage_keys.session);
108
+ if (!access)
109
+ return;
110
+ await revokeSession({ access_token: access }).catch(() => { });
149
111
  }
150
- async getPortalUrl() {
151
- const { access } = this.getCookieTokens();
152
- const res = await fetch(`${SETTINGS.domains.api}/portal/exchange`, {
153
- method: 'GET',
154
- headers: {
155
- Authorization: `Bearer ${access}`,
156
- },
157
- });
158
- if (!res.ok && res.status === 401) {
159
- return { ok: false, url: null, reason: 'unathorized' };
160
- }
161
- else if (!res.ok) {
162
- return { ok: false, url: null, reason: 'fail' };
163
- }
164
- const body = await res.json();
165
- return {
166
- ok: true,
167
- url: `${body.redirectUrl}?code=${body.code}&redirectUrl=${this.config.redirectUrl}`,
168
- reason: null,
112
+ withAuthRequest(handler) {
113
+ return async (req, ctx) => {
114
+ try {
115
+ const session = await verifyRequest({
116
+ request: req,
117
+ appId: this.config.appId,
118
+ });
119
+ return handler(req, ctx, session);
120
+ }
121
+ catch (err) {
122
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
123
+ }
169
124
  };
170
125
  }
171
- /* ------------------------------------------------------------------------ */
172
- /* REFRESH FLOW */
173
- /* ------------------------------------------------------------------------ */
174
- async refreshRequest(refreshToken) {
175
- try {
176
- const response = await fetch(`${SETTINGS.domains.api}/auth/refresh`, {
177
- method: 'POST',
178
- headers: { 'Content-Type': 'application/json' },
179
- body: JSON.stringify({
180
- refresh_token: refreshToken,
181
- client_type: 'server',
182
- }),
183
- });
184
- if (!response.ok)
185
- return null;
186
- return response.json();
187
- }
188
- catch {
189
- return null;
190
- }
191
- }
192
- /* ------------------------------------------------------------------------ */
193
- /* REQUEST WITH AUTO-REFRESH */
194
- /* ------------------------------------------------------------------------ */
195
- async request(url, init = {}) {
126
+ async fetch(url, init = {}) {
196
127
  const { access, refresh } = this.getCookieTokens();
197
- const doFetch = (token) => fetch(url, {
128
+ const finalUrl = url.startsWith('http') ? url : `${this.config.baseUrl}${url}`;
129
+ const doFetch = (token) => fetch(finalUrl, {
198
130
  ...init,
199
131
  headers: {
200
132
  ...init.headers,
@@ -204,9 +136,12 @@ class AuditAuthNext {
204
136
  const start = performance.now();
205
137
  let res = await doFetch(access);
206
138
  if (res.status === 401 && refresh) {
207
- const data = await this.refreshRequest(refresh);
208
- if (data?.access_token && data?.refresh_token) {
209
- res = await doFetch(data.access_token);
139
+ const refreshData = await refreshTokens({ refresh_token: refresh, client_type: 'server' });
140
+ if (!refreshData) {
141
+ return res;
142
+ }
143
+ if (refreshData.access_token && refreshData.refresh_token) {
144
+ res = await doFetch(refreshData.access_token);
210
145
  }
211
146
  }
212
147
  this.pushMetric({
@@ -222,54 +157,29 @@ class AuditAuthNext {
222
157
  });
223
158
  return res;
224
159
  }
225
- /* ------------------------------------------------------------------------ */
226
- /* METRICS */
227
- /* ------------------------------------------------------------------------ */
228
- async metrics(payload) {
229
- await fetch(`${SETTINGS.domains.api}/metrics`, {
230
- method: 'POST',
231
- headers: {
232
- 'Content-Type': 'application/json',
233
- 'x-auditauth-app': this.config.appId,
234
- 'x-auditauth-key': this.config.apiKey,
235
- },
236
- body: JSON.stringify({ ...payload }),
237
- });
238
- return new Response(null, { status: 204 });
239
- }
240
- pushMetric(payload) {
241
- const session_id = this.cookies.get(SETTINGS.cookies.session_id.name);
242
- queueMicrotask(() => {
243
- fetch(`${this.config.baseUrl}${SETTINGS.bff.paths.metrics}`, {
244
- method: 'POST',
245
- headers: { 'Content-Type': 'application/json' },
246
- body: JSON.stringify({ ...payload, session_id }),
247
- }).catch(() => { });
248
- });
249
- }
250
- /* ------------------------------------------------------------------------ */
251
- /* METRICS */
252
- /* ------------------------------------------------------------------------ */
253
160
  async refresh() {
254
161
  const { refresh } = this.getCookieTokens();
255
162
  if (!refresh)
256
163
  return { ok: false };
257
- const result = await this.refreshRequest(refresh);
164
+ const result = await refreshTokens({ refresh_token: refresh, client_type: 'server' });
258
165
  if (!result)
259
166
  return { ok: false };
260
- this.setCookieTokens(result);
167
+ const { access_token, refresh_token, access_expires_seconds, refresh_expires_seconds } = result;
168
+ this.setCookieTokens({
169
+ access_token,
170
+ access_expires_seconds,
171
+ refresh_token,
172
+ refresh_expires_seconds,
173
+ });
261
174
  return { ok: true };
262
175
  }
263
- /* ------------------------------------------------------------------------ */
264
- /* MIDDLEWARE */
265
- /* ------------------------------------------------------------------------ */
266
176
  async middleware(request) {
267
177
  const { access, refresh } = this.getCookieTokens();
268
178
  const url = request.nextUrl;
269
179
  if (access && refresh) {
270
- const sid = this.cookies.get(SETTINGS.cookies.session_id.name);
180
+ const sid = this.cookies.get(SETTINGS.storage_keys.session_id);
271
181
  if (!sid) {
272
- this.cookies.set(SETTINGS.cookies.session_id.name, crypto.randomUUID(), {
182
+ this.cookies.set(SETTINGS.storage_keys.session_id, crypto.randomUUID(), {
273
183
  httpOnly: true,
274
184
  sameSite: 'lax',
275
185
  secure: this.config.baseUrl.startsWith('https'),
@@ -287,15 +197,17 @@ class AuditAuthNext {
287
197
  });
288
198
  return NextResponse.next();
289
199
  }
290
- if (!refresh)
200
+ if (!refresh) {
201
+ if (request.method !== 'GET') {
202
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
203
+ }
291
204
  return NextResponse.redirect(new URL(SETTINGS.bff.paths.login, request.url));
292
- if (refresh && !access)
205
+ }
206
+ if (refresh && !access) {
293
207
  return NextResponse.redirect(new URL(`${SETTINGS.bff.paths.refresh}?redirectUrl=${url}`, request.url));
208
+ }
294
209
  return NextResponse.next();
295
210
  }
296
- /* ------------------------------------------------------------------------ */
297
- /* BFF HANDLERS */
298
- /* ------------------------------------------------------------------------ */
299
211
  getHandlers() {
300
212
  return {
301
213
  GET: async (req, ctx) => {
@@ -304,7 +216,7 @@ class AuditAuthNext {
304
216
  switch (action) {
305
217
  case 'login':
306
218
  {
307
- const url = await this.buildAuthUrl();
219
+ const url = await buildAuthUrl({ apiKey: this.config.apiKey, redirectUrl: `${this.config.baseUrl}/api/auditauth/callback` });
308
220
  return NextResponse.redirect(url);
309
221
  }
310
222
  ;
@@ -313,7 +225,7 @@ class AuditAuthNext {
313
225
  const { ok } = await this.refresh();
314
226
  if (ok)
315
227
  return NextResponse.redirect(redirectUrl || this.config.redirectUrl);
316
- const url = await this.buildAuthUrl();
228
+ const url = await buildAuthUrl({ apiKey: this.config.apiKey, redirectUrl: `${this.config.baseUrl}/api/auditauth/callback` });
317
229
  return NextResponse.redirect(url);
318
230
  }
319
231
  ;
@@ -331,10 +243,16 @@ class AuditAuthNext {
331
243
  ;
332
244
  case 'portal':
333
245
  {
334
- const { ok, url } = await this.getPortalUrl();
335
- return ok && url
336
- ? NextResponse.redirect(url)
337
- : NextResponse.redirect(`${SETTINGS.domains.client}/auth/invalid`);
246
+ const { access } = this.getCookieTokens();
247
+ try {
248
+ if (!access)
249
+ throw new Error('Not auth token');
250
+ const url = await buildPortalUrl({ access_token: access, redirectUrl: this.config.redirectUrl });
251
+ return NextResponse.redirect(url);
252
+ }
253
+ catch (err) {
254
+ return NextResponse.redirect(`${SETTINGS.domains.client}/auth/invalid`);
255
+ }
338
256
  }
339
257
  ;
340
258
  case 'session':
@@ -354,10 +272,34 @@ class AuditAuthNext {
354
272
  },
355
273
  POST: async (req, ctx) => {
356
274
  const action = (await ctx.params).auditauth[0];
357
- if (action === 'metrics') {
358
- return this.metrics(await req.json());
275
+ switch (action) {
276
+ case 'metrics':
277
+ {
278
+ const payload = await req.json();
279
+ await sendMetrics({
280
+ payload,
281
+ appId: this.config.appId,
282
+ apiKey: this.config.apiKey,
283
+ });
284
+ return new Response(null, { status: 204 });
285
+ }
286
+ ;
287
+ case 'refresh':
288
+ {
289
+ const redirectUrl = req.nextUrl.searchParams.get('redirectUrl');
290
+ const { ok } = await this.refresh();
291
+ if (ok)
292
+ return NextResponse.redirect(redirectUrl || this.config.redirectUrl);
293
+ return new Response('Session expired', { status: 401 });
294
+ }
295
+ ;
296
+ default:
297
+ {
298
+ return new Response('not found', { status: 404 });
299
+ }
300
+ ;
359
301
  }
360
- return new Response('not found', { status: 404 });
302
+ ;
361
303
  },
362
304
  };
363
305
  }
@@ -1,10 +1,4 @@
1
1
  declare const SETTINGS: {
2
- readonly jwt_public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2EYs4Q9OyjNuAEPqb4j\nIzc52JdfVcNvEbG43Xp8B2kI9QxwRyX7rtFSwKowj3W1BlCLaTIMK3TafWOf9QwH\nfemuL9Ni37PFcGptzpyuoCYYA650EuD82PENcO49lsObvty2cuXxQszbPPvAecm4\nJ/XG70td/W1UwbjAJcdmp8ktZGYR0JXM37hYA9Xq/aKwu7d0FTL6WdKTvt3L5VxL\nF6WNyLs65ZSbu+j8UEkwmoJ9h9Y0mLQmFtmkoh/HWOFyFDnBNiJX0vRb++RhJw6w\ncrSbqpbTu7z4vIep5lgSOut39P273SVTQZ3cGQIS+605Ur5wjkkSzzaJV1QLBBR9\nAQIDAQAB\n-----END PUBLIC KEY-----\n";
3
- readonly jwt_issuer: "https://api.auditauth.com";
4
- readonly domains: {
5
- readonly api: "https://api.auditauth.com/v1";
6
- readonly client: "https://auditauth.com";
7
- };
8
2
  readonly bff: {
9
3
  readonly paths: {
10
4
  readonly callback: "/api/auditauth/callback";
@@ -14,21 +8,24 @@ declare const SETTINGS: {
14
8
  readonly portal: "/api/auditauth/portal";
15
9
  readonly session: "/api/auditauth/session";
16
10
  readonly refresh: "/api/auditauth/refresh";
11
+ readonly proxy: "/api/auditauth/proxy";
17
12
  };
18
13
  };
19
- readonly cookies: {
20
- readonly access: {
21
- readonly name: "auditauth_access";
22
- };
23
- readonly session: {
24
- readonly name: "auditauth_session";
25
- };
26
- readonly refresh: {
27
- readonly name: "auditauth_refresh";
28
- };
29
- readonly session_id: {
30
- readonly name: "auditauth_sid";
31
- };
14
+ readonly jwt_public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2EYs4Q9OyjNuAEPqb4j\nIzc52JdfVcNvEbG43Xp8B2kI9QxwRyX7rtFSwKowj3W1BlCLaTIMK3TafWOf9QwH\nfemuL9Ni37PFcGptzpyuoCYYA650EuD82PENcO49lsObvty2cuXxQszbPPvAecm4\nJ/XG70td/W1UwbjAJcdmp8ktZGYR0JXM37hYA9Xq/aKwu7d0FTL6WdKTvt3L5VxL\nF6WNyLs65ZSbu+j8UEkwmoJ9h9Y0mLQmFtmkoh/HWOFyFDnBNiJX0vRb++RhJw6w\ncrSbqpbTu7z4vIep5lgSOut39P273SVTQZ3cGQIS+605Ur5wjkkSzzaJV1QLBBR9\nAQIDAQAB\n-----END PUBLIC KEY-----\n";
15
+ readonly jwt_issuer: "https://api.auditauth.com";
16
+ readonly domains: {
17
+ readonly api: "https://api.auditauth.com/v1";
18
+ readonly client: "https://auditauth.com";
19
+ } | {
20
+ readonly api: "http://localhost:4000/v1";
21
+ readonly client: "http://localhost:3000";
22
+ };
23
+ readonly storage_keys: {
24
+ readonly access: "auditauth_access";
25
+ readonly session: "auditauth_session";
26
+ readonly refresh: "auditauth_refresh";
27
+ readonly session_id: "auditauth_sid";
28
+ readonly sync: "__auditauth_sync__";
32
29
  };
33
30
  };
34
31
  export { SETTINGS };
package/dist/settings.js CHANGED
@@ -1,12 +1,6 @@
1
+ import { CORE_SETTINGS } from '@auditauth/core';
1
2
  const SETTINGS = {
2
- jwt_public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2EYs4Q9OyjNuAEPqb4j\nIzc52JdfVcNvEbG43Xp8B2kI9QxwRyX7rtFSwKowj3W1BlCLaTIMK3TafWOf9QwH\nfemuL9Ni37PFcGptzpyuoCYYA650EuD82PENcO49lsObvty2cuXxQszbPPvAecm4\nJ/XG70td/W1UwbjAJcdmp8ktZGYR0JXM37hYA9Xq/aKwu7d0FTL6WdKTvt3L5VxL\nF6WNyLs65ZSbu+j8UEkwmoJ9h9Y0mLQmFtmkoh/HWOFyFDnBNiJX0vRb++RhJw6w\ncrSbqpbTu7z4vIep5lgSOut39P273SVTQZ3cGQIS+605Ur5wjkkSzzaJV1QLBBR9\nAQIDAQAB\n-----END PUBLIC KEY-----\n",
3
- jwt_issuer: "https://api.auditauth.com",
4
- domains: {
5
- api: 'https://api.auditauth.com/v1',
6
- client: 'https://auditauth.com',
7
- // api: 'http://localhost:4000/v1',
8
- // client: 'http://localhost:3000',
9
- },
3
+ ...CORE_SETTINGS,
10
4
  bff: {
11
5
  paths: {
12
6
  callback: '/api/auditauth/callback',
@@ -16,21 +10,8 @@ const SETTINGS = {
16
10
  portal: '/api/auditauth/portal',
17
11
  session: '/api/auditauth/session',
18
12
  refresh: '/api/auditauth/refresh',
13
+ proxy: '/api/auditauth/proxy',
19
14
  }
20
15
  },
21
- cookies: {
22
- access: {
23
- name: 'auditauth_access',
24
- },
25
- session: {
26
- name: 'auditauth_session',
27
- },
28
- refresh: {
29
- name: 'auditauth_refresh',
30
- },
31
- session_id: {
32
- name: 'auditauth_sid',
33
- }
34
- }
35
16
  };
36
17
  export { SETTINGS };
package/dist/types.d.ts CHANGED
@@ -1,23 +1,4 @@
1
- type AuditAuthConfig = {
2
- apiKey: string;
3
- redirectUrl: string;
4
- baseUrl: string;
5
- appId: string;
6
- };
7
- type CredentialResponse = {
8
- access_token: string;
9
- access_expires_seconds: number;
10
- refresh_token: string;
11
- refresh_expires_seconds: number;
12
- };
13
- type SessionUser = {
14
- _id: string;
15
- name: string;
16
- email: string;
17
- avatar: {
18
- url: string | null;
19
- };
20
- };
1
+ import { SessionUser } from "@auditauth/core";
21
2
  type Session = {
22
3
  user: SessionUser;
23
4
  };
@@ -28,28 +9,9 @@ type CookieOptions = {
28
9
  path?: string;
29
10
  maxAge?: number;
30
11
  };
31
- type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
32
- type Metric = {
33
- event_type: 'request';
34
- runtime: 'browser' | 'server';
35
- target: {
36
- type: 'api';
37
- method: RequestMethod;
38
- path: string;
39
- status: number;
40
- duration_ms: number;
41
- };
42
- } | {
43
- event_type: 'navigation';
44
- runtime: 'browser' | 'server';
45
- target: {
46
- type: 'page';
47
- path: string;
48
- };
49
- };
50
12
  type CookieAdapter = {
51
13
  get: (name: string) => string | undefined;
52
14
  set: (name: string, value: string, options?: CookieOptions) => void;
53
15
  remove: (name: string) => void;
54
16
  };
55
- export type { AuditAuthConfig, CredentialResponse, SessionUser, Session, RequestMethod, Metric, CookieOptions, CookieAdapter, };
17
+ export type { Session, CookieOptions, CookieAdapter, };
package/package.json CHANGED
@@ -1,15 +1,36 @@
1
1
  {
2
2
  "name": "@auditauth/next",
3
- "version": "0.1.9",
4
- "description": "Official AuditAuth SDK for Next.js",
3
+ "version": "0.2.0-beta.2",
4
+ "description": "AuditAuth NextJS SDK",
5
5
  "license": "MIT",
6
6
  "author": "Nimibyte",
7
+ "engines": {
8
+ "node": ">=18.18.0"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/nimibyte/auditauth-sdk.git"
13
+ },
14
+ "homepage": "https://docs.auditauth.com",
15
+ "bugs": {
16
+ "url": "https://github.com/nimibyte/auditauth-sdk/issues"
17
+ },
18
+ "keywords": [
19
+ "authentication",
20
+ "auth",
21
+ "oauth",
22
+ "identity",
23
+ "jwt",
24
+ "security",
25
+ "auditauth"
26
+ ],
7
27
  "type": "module",
8
28
  "main": "dist/index.js",
9
29
  "types": "dist/index.d.ts",
10
30
  "files": [
11
31
  "dist"
12
32
  ],
33
+ "sideEffects": false,
13
34
  "exports": {
14
35
  ".": {
15
36
  "types": "./dist/index.d.ts",
@@ -18,8 +39,8 @@
18
39
  },
19
40
  "scripts": {
20
41
  "build": "tsc -p tsconfig.build.json",
21
- "prepublishOnly": "npm run build",
22
- "dev": "tsc -p tsconfig.build.json --watch"
42
+ "dev": "tsc -p tsconfig.build.json --watch",
43
+ "clean": "rm -rf dist"
23
44
  },
24
45
  "peerDependencies": {
25
46
  "next": ">=13",
@@ -27,12 +48,13 @@
27
48
  "react-dom": ">=18"
28
49
  },
29
50
  "dependencies": {
30
- "jose": "^5.2.0"
51
+ "@auditauth/core": "^0.2.0-beta.2",
52
+ "@auditauth/node": "^0.2.0-beta.2"
31
53
  },
32
54
  "devDependencies": {
33
- "@types/node": "^25.0.9",
55
+ "typescript": "^5.9.0",
56
+ "@types/node": "^20",
34
57
  "@types/react": "^19.2.8",
35
- "@types/react-dom": "^19.2.3",
36
- "typescript": "^5.9.3"
58
+ "@types/react-dom": "^19.2.3"
37
59
  }
38
60
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Nimibyte
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.