@auditauth/next 0.2.0-beta.4 → 0.2.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/guard.cjs +41 -16
- package/dist/guard.d.cts +6 -2
- package/dist/guard.d.ts +6 -2
- package/dist/guard.js +42 -17
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/sdk.cjs +122 -36
- package/dist/sdk.d.cts +5 -0
- package/dist/sdk.d.ts +5 -0
- package/dist/sdk.js +124 -37
- package/dist/settings.d.cts +3 -3
- package/dist/settings.d.ts +3 -3
- package/package.json +3 -3
package/dist/guard.cjs
CHANGED
|
@@ -25,47 +25,72 @@ __export(guard_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(guard_exports);
|
|
26
26
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
27
27
|
var import_react = require("react");
|
|
28
|
+
var import_navigation = require("next/navigation");
|
|
28
29
|
var import_settings = require("./settings.cjs");
|
|
29
30
|
const AuthContext = (0, import_react.createContext)(null);
|
|
30
31
|
const useAuditAuth = () => {
|
|
31
32
|
const ctx = (0, import_react.useContext)(AuthContext);
|
|
32
33
|
if (!ctx) {
|
|
33
|
-
throw new Error("useAuditAuth must be used within
|
|
34
|
+
throw new Error("useAuditAuth must be used within AuditAuthGuard");
|
|
34
35
|
}
|
|
35
36
|
return ctx;
|
|
36
37
|
};
|
|
37
|
-
const
|
|
38
|
-
|
|
38
|
+
const defaultFallback = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "Verificando sesion..." });
|
|
39
|
+
const AuditAuthGuard = ({
|
|
40
|
+
children,
|
|
41
|
+
fallback,
|
|
42
|
+
unauthenticatedFallback,
|
|
43
|
+
mode = "redirect"
|
|
44
|
+
}) => {
|
|
45
|
+
const router = (0, import_navigation.useRouter)();
|
|
46
|
+
const [state, setState] = (0, import_react.useState)({
|
|
47
|
+
status: "loading",
|
|
48
|
+
user: null
|
|
49
|
+
});
|
|
39
50
|
(0, import_react.useEffect)(() => {
|
|
40
|
-
|
|
51
|
+
const controller = new AbortController();
|
|
41
52
|
const checkSession = async () => {
|
|
42
53
|
try {
|
|
43
54
|
const response = await fetch(import_settings.SETTINGS.bff.paths.session, {
|
|
44
55
|
credentials: "include",
|
|
45
|
-
cache: "no-store"
|
|
56
|
+
cache: "no-store",
|
|
57
|
+
signal: controller.signal
|
|
46
58
|
});
|
|
47
|
-
if (cancelled) return;
|
|
48
59
|
if (!response.ok) {
|
|
49
|
-
|
|
60
|
+
setState({ status: "unauthenticated", user: null });
|
|
61
|
+
if (mode === "redirect") router.replace(import_settings.SETTINGS.bff.paths.login);
|
|
50
62
|
return;
|
|
51
63
|
}
|
|
52
64
|
const data = await response.json();
|
|
53
|
-
|
|
65
|
+
if (!data?.user || typeof data.user !== "object") {
|
|
66
|
+
setState({ status: "unauthenticated", user: null });
|
|
67
|
+
if (mode === "redirect") router.replace(import_settings.SETTINGS.bff.paths.login);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setState({ status: "authenticated", user: data.user });
|
|
54
71
|
} catch {
|
|
55
|
-
|
|
72
|
+
if (controller.signal.aborted) return;
|
|
73
|
+
setState({ status: "unauthenticated", user: null });
|
|
74
|
+
if (mode === "redirect") router.replace(import_settings.SETTINGS.bff.paths.login);
|
|
56
75
|
return;
|
|
57
76
|
}
|
|
58
77
|
};
|
|
59
78
|
checkSession();
|
|
60
79
|
return () => {
|
|
61
|
-
|
|
80
|
+
controller.abort();
|
|
62
81
|
};
|
|
63
|
-
}, []);
|
|
64
|
-
const value = (0, import_react.useMemo)(() =>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
}, [mode, router]);
|
|
83
|
+
const value = (0, import_react.useMemo)(() => {
|
|
84
|
+
if (state.status !== "authenticated") return null;
|
|
85
|
+
return { user: state.user };
|
|
86
|
+
}, [state]);
|
|
87
|
+
if (!value) {
|
|
88
|
+
if (state.status === "unauthenticated") {
|
|
89
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: unauthenticatedFallback ?? fallback ?? defaultFallback });
|
|
90
|
+
}
|
|
91
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback ?? defaultFallback });
|
|
92
|
+
}
|
|
93
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value, children });
|
|
69
94
|
};
|
|
70
95
|
// Annotate the CommonJS export names for ESM import in node:
|
|
71
96
|
0 && (module.exports = {
|
package/dist/guard.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
import { SessionUser } from '@auditauth/core';
|
|
3
4
|
|
|
4
5
|
type AuthContextValue = {
|
|
@@ -6,8 +7,11 @@ type AuthContextValue = {
|
|
|
6
7
|
};
|
|
7
8
|
declare const useAuditAuth: () => AuthContextValue;
|
|
8
9
|
type AuditAuthGuardProps = {
|
|
9
|
-
children:
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
fallback?: ReactNode;
|
|
12
|
+
unauthenticatedFallback?: ReactNode;
|
|
13
|
+
mode?: 'redirect' | 'fallback';
|
|
10
14
|
};
|
|
11
|
-
declare const AuditAuthGuard: (
|
|
15
|
+
declare const AuditAuthGuard: ({ children, fallback, unauthenticatedFallback, mode, }: AuditAuthGuardProps) => react_jsx_runtime.JSX.Element;
|
|
12
16
|
|
|
13
17
|
export { AuditAuthGuard, useAuditAuth };
|
package/dist/guard.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
import { SessionUser } from '@auditauth/core';
|
|
3
4
|
|
|
4
5
|
type AuthContextValue = {
|
|
@@ -6,8 +7,11 @@ type AuthContextValue = {
|
|
|
6
7
|
};
|
|
7
8
|
declare const useAuditAuth: () => AuthContextValue;
|
|
8
9
|
type AuditAuthGuardProps = {
|
|
9
|
-
children:
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
fallback?: ReactNode;
|
|
12
|
+
unauthenticatedFallback?: ReactNode;
|
|
13
|
+
mode?: 'redirect' | 'fallback';
|
|
10
14
|
};
|
|
11
|
-
declare const AuditAuthGuard: (
|
|
15
|
+
declare const AuditAuthGuard: ({ children, fallback, unauthenticatedFallback, mode, }: AuditAuthGuardProps) => react_jsx_runtime.JSX.Element;
|
|
12
16
|
|
|
13
17
|
export { AuditAuthGuard, useAuditAuth };
|
package/dist/guard.js
CHANGED
|
@@ -1,47 +1,72 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
4
5
|
import { SETTINGS } from "./settings.js";
|
|
5
6
|
const AuthContext = createContext(null);
|
|
6
7
|
const useAuditAuth = () => {
|
|
7
8
|
const ctx = useContext(AuthContext);
|
|
8
9
|
if (!ctx) {
|
|
9
|
-
throw new Error("useAuditAuth must be used within
|
|
10
|
+
throw new Error("useAuditAuth must be used within AuditAuthGuard");
|
|
10
11
|
}
|
|
11
12
|
return ctx;
|
|
12
13
|
};
|
|
13
|
-
const
|
|
14
|
-
|
|
14
|
+
const defaultFallback = /* @__PURE__ */ jsx("div", { children: "Verificando sesion..." });
|
|
15
|
+
const AuditAuthGuard = ({
|
|
16
|
+
children,
|
|
17
|
+
fallback,
|
|
18
|
+
unauthenticatedFallback,
|
|
19
|
+
mode = "redirect"
|
|
20
|
+
}) => {
|
|
21
|
+
const router = useRouter();
|
|
22
|
+
const [state, setState] = useState({
|
|
23
|
+
status: "loading",
|
|
24
|
+
user: null
|
|
25
|
+
});
|
|
15
26
|
useEffect(() => {
|
|
16
|
-
|
|
27
|
+
const controller = new AbortController();
|
|
17
28
|
const checkSession = async () => {
|
|
18
29
|
try {
|
|
19
30
|
const response = await fetch(SETTINGS.bff.paths.session, {
|
|
20
31
|
credentials: "include",
|
|
21
|
-
cache: "no-store"
|
|
32
|
+
cache: "no-store",
|
|
33
|
+
signal: controller.signal
|
|
22
34
|
});
|
|
23
|
-
if (cancelled) return;
|
|
24
35
|
if (!response.ok) {
|
|
25
|
-
|
|
36
|
+
setState({ status: "unauthenticated", user: null });
|
|
37
|
+
if (mode === "redirect") router.replace(SETTINGS.bff.paths.login);
|
|
26
38
|
return;
|
|
27
39
|
}
|
|
28
40
|
const data = await response.json();
|
|
29
|
-
|
|
41
|
+
if (!data?.user || typeof data.user !== "object") {
|
|
42
|
+
setState({ status: "unauthenticated", user: null });
|
|
43
|
+
if (mode === "redirect") router.replace(SETTINGS.bff.paths.login);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setState({ status: "authenticated", user: data.user });
|
|
30
47
|
} catch {
|
|
31
|
-
|
|
48
|
+
if (controller.signal.aborted) return;
|
|
49
|
+
setState({ status: "unauthenticated", user: null });
|
|
50
|
+
if (mode === "redirect") router.replace(SETTINGS.bff.paths.login);
|
|
32
51
|
return;
|
|
33
52
|
}
|
|
34
53
|
};
|
|
35
54
|
checkSession();
|
|
36
55
|
return () => {
|
|
37
|
-
|
|
56
|
+
controller.abort();
|
|
38
57
|
};
|
|
39
|
-
}, []);
|
|
40
|
-
const value = useMemo(() =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
}, [mode, router]);
|
|
59
|
+
const value = useMemo(() => {
|
|
60
|
+
if (state.status !== "authenticated") return null;
|
|
61
|
+
return { user: state.user };
|
|
62
|
+
}, [state]);
|
|
63
|
+
if (!value) {
|
|
64
|
+
if (state.status === "unauthenticated") {
|
|
65
|
+
return /* @__PURE__ */ jsx(Fragment, { children: unauthenticatedFallback ?? fallback ?? defaultFallback });
|
|
66
|
+
}
|
|
67
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? defaultFallback });
|
|
68
|
+
}
|
|
69
|
+
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
|
|
45
70
|
};
|
|
46
71
|
export {
|
|
47
72
|
AuditAuthGuard,
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/sdk.cjs
CHANGED
|
@@ -26,6 +26,9 @@ var import_server = require("next/server.js");
|
|
|
26
26
|
var import_settings = require("./settings.cjs");
|
|
27
27
|
var import_core = require("@auditauth/core");
|
|
28
28
|
var import_node = require("@auditauth/node");
|
|
29
|
+
const CALLBACK_CODE_COOKIE = "auditauth_last_code";
|
|
30
|
+
const CALLBACK_CODE_TTL_SECONDS = 120;
|
|
31
|
+
const callbackInFlight = /* @__PURE__ */ new Map();
|
|
29
32
|
class AuditAuthNext {
|
|
30
33
|
constructor(config, cookies) {
|
|
31
34
|
if (!config.appId) throw new Error("Missing appId");
|
|
@@ -44,8 +47,32 @@ class AuditAuthNext {
|
|
|
44
47
|
refresh: this.cookies.get(import_settings.SETTINGS.storage_keys.refresh)
|
|
45
48
|
};
|
|
46
49
|
}
|
|
50
|
+
isSecureCookie() {
|
|
51
|
+
return this.config.redirectUrl.startsWith("https://");
|
|
52
|
+
}
|
|
53
|
+
hasAuthCookies() {
|
|
54
|
+
const { access, refresh } = this.getCookieTokens();
|
|
55
|
+
return !!(access && refresh);
|
|
56
|
+
}
|
|
57
|
+
getLastAuthorizedCode() {
|
|
58
|
+
return this.cookies.get(CALLBACK_CODE_COOKIE);
|
|
59
|
+
}
|
|
60
|
+
setLastAuthorizedCode(code) {
|
|
61
|
+
this.cookies.set(CALLBACK_CODE_COOKIE, code, {
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
sameSite: "lax",
|
|
64
|
+
secure: this.isSecureCookie(),
|
|
65
|
+
path: "/",
|
|
66
|
+
maxAge: CALLBACK_CODE_TTL_SECONDS
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
isDuplicateCodeError(error) {
|
|
70
|
+
if (!(error instanceof Error)) return false;
|
|
71
|
+
const message = error.message.toLowerCase();
|
|
72
|
+
return message.includes("code not found");
|
|
73
|
+
}
|
|
47
74
|
setCookieTokens(params) {
|
|
48
|
-
const isSecure = this.
|
|
75
|
+
const isSecure = this.isSecureCookie();
|
|
49
76
|
this.cookies.set(
|
|
50
77
|
import_settings.SETTINGS.storage_keys.access,
|
|
51
78
|
params.access_token,
|
|
@@ -102,36 +129,61 @@ class AuditAuthNext {
|
|
|
102
129
|
}
|
|
103
130
|
async callback(request) {
|
|
104
131
|
const code = new URL(request.url).searchParams.get("code");
|
|
105
|
-
|
|
106
|
-
const { data } = await (0, import_core.authorizeCode)({ code, client_type: "server" });
|
|
107
|
-
const session = {
|
|
108
|
-
user: data.user
|
|
109
|
-
};
|
|
110
|
-
const isSecure = this.config.redirectUrl.includes("http");
|
|
111
|
-
this.cookies.set(
|
|
112
|
-
import_settings.SETTINGS.storage_keys.session,
|
|
113
|
-
JSON.stringify(session),
|
|
114
|
-
{
|
|
115
|
-
httpOnly: true,
|
|
116
|
-
sameSite: "lax",
|
|
117
|
-
secure: isSecure,
|
|
118
|
-
path: "/",
|
|
119
|
-
maxAge: data.refresh_expires_seconds - 60
|
|
120
|
-
}
|
|
121
|
-
);
|
|
122
|
-
this.setCookieTokens({
|
|
123
|
-
access_token: data.access_token,
|
|
124
|
-
access_expires_seconds: data.access_expires_seconds,
|
|
125
|
-
refresh_token: data.refresh_token,
|
|
126
|
-
refresh_expires_seconds: data.refresh_expires_seconds
|
|
127
|
-
});
|
|
128
|
-
return { ok: true, url: this.config.redirectUrl };
|
|
129
|
-
} catch {
|
|
132
|
+
if (!code) {
|
|
130
133
|
return {
|
|
131
134
|
ok: false,
|
|
132
135
|
url: `${import_settings.SETTINGS.domains.client}/auth/invalid?reason=wrong_config`
|
|
133
136
|
};
|
|
134
137
|
}
|
|
138
|
+
const lastAuthorizedCode = this.getLastAuthorizedCode();
|
|
139
|
+
if (lastAuthorizedCode === code && (this.hasSession() || this.hasAuthCookies())) {
|
|
140
|
+
return { ok: true, url: this.config.redirectUrl };
|
|
141
|
+
}
|
|
142
|
+
const inFlight = callbackInFlight.get(code);
|
|
143
|
+
if (inFlight) return inFlight;
|
|
144
|
+
const authorizeRequest = (async () => {
|
|
145
|
+
try {
|
|
146
|
+
const { data } = await (0, import_core.authorizeCode)({ code, client_type: "server" });
|
|
147
|
+
const session = {
|
|
148
|
+
user: data.user
|
|
149
|
+
};
|
|
150
|
+
const isSecure = this.isSecureCookie();
|
|
151
|
+
this.cookies.set(
|
|
152
|
+
import_settings.SETTINGS.storage_keys.session,
|
|
153
|
+
JSON.stringify(session),
|
|
154
|
+
{
|
|
155
|
+
httpOnly: true,
|
|
156
|
+
sameSite: "lax",
|
|
157
|
+
secure: isSecure,
|
|
158
|
+
path: "/",
|
|
159
|
+
maxAge: data.refresh_expires_seconds - 60
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
this.setCookieTokens({
|
|
163
|
+
access_token: data.access_token,
|
|
164
|
+
access_expires_seconds: data.access_expires_seconds,
|
|
165
|
+
refresh_token: data.refresh_token,
|
|
166
|
+
refresh_expires_seconds: data.refresh_expires_seconds
|
|
167
|
+
});
|
|
168
|
+
this.setLastAuthorizedCode(code);
|
|
169
|
+
return { ok: true, url: this.config.redirectUrl };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (this.isDuplicateCodeError(error) && (this.hasSession() || this.hasAuthCookies() || this.getLastAuthorizedCode() === code)) {
|
|
172
|
+
this.setLastAuthorizedCode(code);
|
|
173
|
+
return { ok: true, url: this.config.redirectUrl };
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
url: `${import_settings.SETTINGS.domains.client}/auth/invalid?reason=wrong_config`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
callbackInFlight.set(code, authorizeRequest);
|
|
182
|
+
try {
|
|
183
|
+
return await authorizeRequest;
|
|
184
|
+
} finally {
|
|
185
|
+
callbackInFlight.delete(code);
|
|
186
|
+
}
|
|
135
187
|
}
|
|
136
188
|
async logout() {
|
|
137
189
|
const { access } = this.getCookieTokens();
|
|
@@ -246,15 +298,27 @@ class AuditAuthNext {
|
|
|
246
298
|
switch (action) {
|
|
247
299
|
case "login":
|
|
248
300
|
{
|
|
249
|
-
|
|
250
|
-
|
|
301
|
+
try {
|
|
302
|
+
const url = await (0, import_core.buildAuthUrl)({
|
|
303
|
+
apiKey: this.config.apiKey,
|
|
304
|
+
redirectUrl: `${this.config.baseUrl}/api/auditauth/callback`,
|
|
305
|
+
cancelUrl: this.config.baseUrl
|
|
306
|
+
});
|
|
307
|
+
return import_server.NextResponse.redirect(url);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
return new Response("Invalid session", { status: 401 });
|
|
310
|
+
}
|
|
251
311
|
}
|
|
252
312
|
;
|
|
253
313
|
case "refresh":
|
|
254
314
|
{
|
|
255
|
-
const { ok
|
|
256
|
-
if (
|
|
257
|
-
const url = await (0, import_core.buildAuthUrl)({
|
|
315
|
+
const { ok } = await this.refresh();
|
|
316
|
+
if (ok) return import_server.NextResponse.redirect(redirectUrl || this.config.redirectUrl);
|
|
317
|
+
const url = await (0, import_core.buildAuthUrl)({
|
|
318
|
+
apiKey: this.config.apiKey,
|
|
319
|
+
redirectUrl: `${this.config.baseUrl}/api/auditauth/callback`,
|
|
320
|
+
cancelUrl: this.config.baseUrl
|
|
321
|
+
});
|
|
258
322
|
return import_server.NextResponse.redirect(url);
|
|
259
323
|
}
|
|
260
324
|
;
|
|
@@ -267,7 +331,7 @@ class AuditAuthNext {
|
|
|
267
331
|
case "logout":
|
|
268
332
|
{
|
|
269
333
|
await this.logout();
|
|
270
|
-
return import_server.NextResponse.redirect(this.config.
|
|
334
|
+
return import_server.NextResponse.redirect(this.config.baseUrl);
|
|
271
335
|
}
|
|
272
336
|
;
|
|
273
337
|
case "portal":
|
|
@@ -285,8 +349,30 @@ class AuditAuthNext {
|
|
|
285
349
|
case "session":
|
|
286
350
|
{
|
|
287
351
|
const user = this.getSession();
|
|
288
|
-
if (
|
|
289
|
-
|
|
352
|
+
if (user) return import_server.NextResponse.json({ user });
|
|
353
|
+
try {
|
|
354
|
+
const { access } = this.getCookieTokens();
|
|
355
|
+
if (!access) throw new Error("Not auth token");
|
|
356
|
+
const refreshedUser = await (0, import_core.getSessionUser)({ access_token: access });
|
|
357
|
+
const session = {
|
|
358
|
+
user: refreshedUser
|
|
359
|
+
};
|
|
360
|
+
const isSecure = this.isSecureCookie();
|
|
361
|
+
this.cookies.set(
|
|
362
|
+
import_settings.SETTINGS.storage_keys.session,
|
|
363
|
+
JSON.stringify(session),
|
|
364
|
+
{
|
|
365
|
+
httpOnly: true,
|
|
366
|
+
sameSite: "lax",
|
|
367
|
+
secure: isSecure,
|
|
368
|
+
path: "/",
|
|
369
|
+
maxAge: 24 * 60 * 60 * 1e3 * 3
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
return import_server.NextResponse.json({ user: refreshedUser });
|
|
373
|
+
} catch (err) {
|
|
374
|
+
return new import_server.NextResponse(null, { status: 401 });
|
|
375
|
+
}
|
|
290
376
|
}
|
|
291
377
|
;
|
|
292
378
|
default:
|
|
@@ -313,8 +399,8 @@ class AuditAuthNext {
|
|
|
313
399
|
case "refresh":
|
|
314
400
|
{
|
|
315
401
|
const redirectUrl = req.nextUrl.searchParams.get("redirectUrl");
|
|
316
|
-
const { ok
|
|
317
|
-
if (
|
|
402
|
+
const { ok } = await this.refresh();
|
|
403
|
+
if (ok) return import_server.NextResponse.redirect(redirectUrl || this.config.redirectUrl);
|
|
318
404
|
return new Response("Session expired", { status: 401 });
|
|
319
405
|
}
|
|
320
406
|
;
|
package/dist/sdk.d.cts
CHANGED
|
@@ -8,6 +8,11 @@ declare class AuditAuthNext {
|
|
|
8
8
|
private cookies;
|
|
9
9
|
constructor(config: AuditAuthConfig, cookies: CookieAdapter);
|
|
10
10
|
private getCookieTokens;
|
|
11
|
+
private isSecureCookie;
|
|
12
|
+
private hasAuthCookies;
|
|
13
|
+
private getLastAuthorizedCode;
|
|
14
|
+
private setLastAuthorizedCode;
|
|
15
|
+
private isDuplicateCodeError;
|
|
11
16
|
private setCookieTokens;
|
|
12
17
|
private pushMetric;
|
|
13
18
|
getSession(): SessionUser | null;
|
package/dist/sdk.d.ts
CHANGED
|
@@ -8,6 +8,11 @@ declare class AuditAuthNext {
|
|
|
8
8
|
private cookies;
|
|
9
9
|
constructor(config: AuditAuthConfig, cookies: CookieAdapter);
|
|
10
10
|
private getCookieTokens;
|
|
11
|
+
private isSecureCookie;
|
|
12
|
+
private hasAuthCookies;
|
|
13
|
+
private getLastAuthorizedCode;
|
|
14
|
+
private setLastAuthorizedCode;
|
|
15
|
+
private isDuplicateCodeError;
|
|
11
16
|
private setCookieTokens;
|
|
12
17
|
private pushMetric;
|
|
13
18
|
getSession(): SessionUser | null;
|
package/dist/sdk.js
CHANGED
|
@@ -7,9 +7,13 @@ import {
|
|
|
7
7
|
revokeSession,
|
|
8
8
|
buildPortalUrl,
|
|
9
9
|
refreshTokens,
|
|
10
|
-
sendMetrics
|
|
10
|
+
sendMetrics,
|
|
11
|
+
getSessionUser
|
|
11
12
|
} from "@auditauth/core";
|
|
12
13
|
import { verifyRequest } from "@auditauth/node";
|
|
14
|
+
const CALLBACK_CODE_COOKIE = "auditauth_last_code";
|
|
15
|
+
const CALLBACK_CODE_TTL_SECONDS = 120;
|
|
16
|
+
const callbackInFlight = /* @__PURE__ */ new Map();
|
|
13
17
|
class AuditAuthNext {
|
|
14
18
|
constructor(config, cookies) {
|
|
15
19
|
if (!config.appId) throw new Error("Missing appId");
|
|
@@ -28,8 +32,32 @@ class AuditAuthNext {
|
|
|
28
32
|
refresh: this.cookies.get(SETTINGS.storage_keys.refresh)
|
|
29
33
|
};
|
|
30
34
|
}
|
|
35
|
+
isSecureCookie() {
|
|
36
|
+
return this.config.redirectUrl.startsWith("https://");
|
|
37
|
+
}
|
|
38
|
+
hasAuthCookies() {
|
|
39
|
+
const { access, refresh } = this.getCookieTokens();
|
|
40
|
+
return !!(access && refresh);
|
|
41
|
+
}
|
|
42
|
+
getLastAuthorizedCode() {
|
|
43
|
+
return this.cookies.get(CALLBACK_CODE_COOKIE);
|
|
44
|
+
}
|
|
45
|
+
setLastAuthorizedCode(code) {
|
|
46
|
+
this.cookies.set(CALLBACK_CODE_COOKIE, code, {
|
|
47
|
+
httpOnly: true,
|
|
48
|
+
sameSite: "lax",
|
|
49
|
+
secure: this.isSecureCookie(),
|
|
50
|
+
path: "/",
|
|
51
|
+
maxAge: CALLBACK_CODE_TTL_SECONDS
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
isDuplicateCodeError(error) {
|
|
55
|
+
if (!(error instanceof Error)) return false;
|
|
56
|
+
const message = error.message.toLowerCase();
|
|
57
|
+
return message.includes("code not found");
|
|
58
|
+
}
|
|
31
59
|
setCookieTokens(params) {
|
|
32
|
-
const isSecure = this.
|
|
60
|
+
const isSecure = this.isSecureCookie();
|
|
33
61
|
this.cookies.set(
|
|
34
62
|
SETTINGS.storage_keys.access,
|
|
35
63
|
params.access_token,
|
|
@@ -86,36 +114,61 @@ class AuditAuthNext {
|
|
|
86
114
|
}
|
|
87
115
|
async callback(request) {
|
|
88
116
|
const code = new URL(request.url).searchParams.get("code");
|
|
89
|
-
|
|
90
|
-
const { data } = await authorizeCode({ code, client_type: "server" });
|
|
91
|
-
const session = {
|
|
92
|
-
user: data.user
|
|
93
|
-
};
|
|
94
|
-
const isSecure = this.config.redirectUrl.includes("http");
|
|
95
|
-
this.cookies.set(
|
|
96
|
-
SETTINGS.storage_keys.session,
|
|
97
|
-
JSON.stringify(session),
|
|
98
|
-
{
|
|
99
|
-
httpOnly: true,
|
|
100
|
-
sameSite: "lax",
|
|
101
|
-
secure: isSecure,
|
|
102
|
-
path: "/",
|
|
103
|
-
maxAge: data.refresh_expires_seconds - 60
|
|
104
|
-
}
|
|
105
|
-
);
|
|
106
|
-
this.setCookieTokens({
|
|
107
|
-
access_token: data.access_token,
|
|
108
|
-
access_expires_seconds: data.access_expires_seconds,
|
|
109
|
-
refresh_token: data.refresh_token,
|
|
110
|
-
refresh_expires_seconds: data.refresh_expires_seconds
|
|
111
|
-
});
|
|
112
|
-
return { ok: true, url: this.config.redirectUrl };
|
|
113
|
-
} catch {
|
|
117
|
+
if (!code) {
|
|
114
118
|
return {
|
|
115
119
|
ok: false,
|
|
116
120
|
url: `${SETTINGS.domains.client}/auth/invalid?reason=wrong_config`
|
|
117
121
|
};
|
|
118
122
|
}
|
|
123
|
+
const lastAuthorizedCode = this.getLastAuthorizedCode();
|
|
124
|
+
if (lastAuthorizedCode === code && (this.hasSession() || this.hasAuthCookies())) {
|
|
125
|
+
return { ok: true, url: this.config.redirectUrl };
|
|
126
|
+
}
|
|
127
|
+
const inFlight = callbackInFlight.get(code);
|
|
128
|
+
if (inFlight) return inFlight;
|
|
129
|
+
const authorizeRequest = (async () => {
|
|
130
|
+
try {
|
|
131
|
+
const { data } = await authorizeCode({ code, client_type: "server" });
|
|
132
|
+
const session = {
|
|
133
|
+
user: data.user
|
|
134
|
+
};
|
|
135
|
+
const isSecure = this.isSecureCookie();
|
|
136
|
+
this.cookies.set(
|
|
137
|
+
SETTINGS.storage_keys.session,
|
|
138
|
+
JSON.stringify(session),
|
|
139
|
+
{
|
|
140
|
+
httpOnly: true,
|
|
141
|
+
sameSite: "lax",
|
|
142
|
+
secure: isSecure,
|
|
143
|
+
path: "/",
|
|
144
|
+
maxAge: data.refresh_expires_seconds - 60
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
this.setCookieTokens({
|
|
148
|
+
access_token: data.access_token,
|
|
149
|
+
access_expires_seconds: data.access_expires_seconds,
|
|
150
|
+
refresh_token: data.refresh_token,
|
|
151
|
+
refresh_expires_seconds: data.refresh_expires_seconds
|
|
152
|
+
});
|
|
153
|
+
this.setLastAuthorizedCode(code);
|
|
154
|
+
return { ok: true, url: this.config.redirectUrl };
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (this.isDuplicateCodeError(error) && (this.hasSession() || this.hasAuthCookies() || this.getLastAuthorizedCode() === code)) {
|
|
157
|
+
this.setLastAuthorizedCode(code);
|
|
158
|
+
return { ok: true, url: this.config.redirectUrl };
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
url: `${SETTINGS.domains.client}/auth/invalid?reason=wrong_config`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
})();
|
|
166
|
+
callbackInFlight.set(code, authorizeRequest);
|
|
167
|
+
try {
|
|
168
|
+
return await authorizeRequest;
|
|
169
|
+
} finally {
|
|
170
|
+
callbackInFlight.delete(code);
|
|
171
|
+
}
|
|
119
172
|
}
|
|
120
173
|
async logout() {
|
|
121
174
|
const { access } = this.getCookieTokens();
|
|
@@ -230,15 +283,27 @@ class AuditAuthNext {
|
|
|
230
283
|
switch (action) {
|
|
231
284
|
case "login":
|
|
232
285
|
{
|
|
233
|
-
|
|
234
|
-
|
|
286
|
+
try {
|
|
287
|
+
const url = await buildAuthUrl({
|
|
288
|
+
apiKey: this.config.apiKey,
|
|
289
|
+
redirectUrl: `${this.config.baseUrl}/api/auditauth/callback`,
|
|
290
|
+
cancelUrl: this.config.baseUrl
|
|
291
|
+
});
|
|
292
|
+
return NextResponse.redirect(url);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
return new Response("Invalid session", { status: 401 });
|
|
295
|
+
}
|
|
235
296
|
}
|
|
236
297
|
;
|
|
237
298
|
case "refresh":
|
|
238
299
|
{
|
|
239
|
-
const { ok
|
|
240
|
-
if (
|
|
241
|
-
const url = await buildAuthUrl({
|
|
300
|
+
const { ok } = await this.refresh();
|
|
301
|
+
if (ok) return NextResponse.redirect(redirectUrl || this.config.redirectUrl);
|
|
302
|
+
const url = await buildAuthUrl({
|
|
303
|
+
apiKey: this.config.apiKey,
|
|
304
|
+
redirectUrl: `${this.config.baseUrl}/api/auditauth/callback`,
|
|
305
|
+
cancelUrl: this.config.baseUrl
|
|
306
|
+
});
|
|
242
307
|
return NextResponse.redirect(url);
|
|
243
308
|
}
|
|
244
309
|
;
|
|
@@ -251,7 +316,7 @@ class AuditAuthNext {
|
|
|
251
316
|
case "logout":
|
|
252
317
|
{
|
|
253
318
|
await this.logout();
|
|
254
|
-
return NextResponse.redirect(this.config.
|
|
319
|
+
return NextResponse.redirect(this.config.baseUrl);
|
|
255
320
|
}
|
|
256
321
|
;
|
|
257
322
|
case "portal":
|
|
@@ -269,8 +334,30 @@ class AuditAuthNext {
|
|
|
269
334
|
case "session":
|
|
270
335
|
{
|
|
271
336
|
const user = this.getSession();
|
|
272
|
-
if (
|
|
273
|
-
|
|
337
|
+
if (user) return NextResponse.json({ user });
|
|
338
|
+
try {
|
|
339
|
+
const { access } = this.getCookieTokens();
|
|
340
|
+
if (!access) throw new Error("Not auth token");
|
|
341
|
+
const refreshedUser = await getSessionUser({ access_token: access });
|
|
342
|
+
const session = {
|
|
343
|
+
user: refreshedUser
|
|
344
|
+
};
|
|
345
|
+
const isSecure = this.isSecureCookie();
|
|
346
|
+
this.cookies.set(
|
|
347
|
+
SETTINGS.storage_keys.session,
|
|
348
|
+
JSON.stringify(session),
|
|
349
|
+
{
|
|
350
|
+
httpOnly: true,
|
|
351
|
+
sameSite: "lax",
|
|
352
|
+
secure: isSecure,
|
|
353
|
+
path: "/",
|
|
354
|
+
maxAge: 24 * 60 * 60 * 1e3 * 3
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
return NextResponse.json({ user: refreshedUser });
|
|
358
|
+
} catch (err) {
|
|
359
|
+
return new NextResponse(null, { status: 401 });
|
|
360
|
+
}
|
|
274
361
|
}
|
|
275
362
|
;
|
|
276
363
|
default:
|
|
@@ -297,8 +384,8 @@ class AuditAuthNext {
|
|
|
297
384
|
case "refresh":
|
|
298
385
|
{
|
|
299
386
|
const redirectUrl = req.nextUrl.searchParams.get("redirectUrl");
|
|
300
|
-
const { ok
|
|
301
|
-
if (
|
|
387
|
+
const { ok } = await this.refresh();
|
|
388
|
+
if (ok) return NextResponse.redirect(redirectUrl || this.config.redirectUrl);
|
|
302
389
|
return new Response("Session expired", { status: 401 });
|
|
303
390
|
}
|
|
304
391
|
;
|
package/dist/settings.d.cts
CHANGED
|
@@ -14,11 +14,11 @@ declare const SETTINGS: {
|
|
|
14
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
15
|
readonly jwt_issuer: "https://api.auditauth.com";
|
|
16
16
|
readonly domains: {
|
|
17
|
-
readonly api: "http://localhost:4000/v1";
|
|
18
|
-
readonly client: "http://localhost:3000";
|
|
19
|
-
} | {
|
|
20
17
|
readonly api: "https://api.auditauth.com/v1";
|
|
21
18
|
readonly client: "https://auditauth.com";
|
|
19
|
+
} | {
|
|
20
|
+
readonly api: "http://localhost:4000/v1";
|
|
21
|
+
readonly client: "https://localhost:3000";
|
|
22
22
|
};
|
|
23
23
|
readonly storage_keys: {
|
|
24
24
|
readonly access: "auditauth_access";
|
package/dist/settings.d.ts
CHANGED
|
@@ -14,11 +14,11 @@ declare const SETTINGS: {
|
|
|
14
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
15
|
readonly jwt_issuer: "https://api.auditauth.com";
|
|
16
16
|
readonly domains: {
|
|
17
|
-
readonly api: "http://localhost:4000/v1";
|
|
18
|
-
readonly client: "http://localhost:3000";
|
|
19
|
-
} | {
|
|
20
17
|
readonly api: "https://api.auditauth.com/v1";
|
|
21
18
|
readonly client: "https://auditauth.com";
|
|
19
|
+
} | {
|
|
20
|
+
readonly api: "http://localhost:4000/v1";
|
|
21
|
+
readonly client: "https://localhost:3000";
|
|
22
22
|
};
|
|
23
23
|
readonly storage_keys: {
|
|
24
24
|
readonly access: "auditauth_access";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auditauth/next",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.6",
|
|
4
4
|
"description": "AuditAuth NextJS SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nimibyte",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"react-dom": ">=18"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@auditauth/core": "^0.2.0-beta.
|
|
57
|
-
"@auditauth/node": "^0.2.0-beta.
|
|
56
|
+
"@auditauth/core": "^0.2.0-beta.6",
|
|
57
|
+
"@auditauth/node": "^0.2.0-beta.6"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"typescript": "^5.9.0",
|