@augmenting-integrations/auth 4.0.2 → 4.1.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/dist/index.cjs +108 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +103 -11
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/LICENSE +0 -21
- package/dist/index.d.ts +0 -78
- package/dist/index.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -30,12 +30,84 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
33
|
+
AuthError: () => AuthError,
|
|
34
|
+
createAuth: () => createAuth,
|
|
35
|
+
getUserGroups: () => getUserGroups,
|
|
36
|
+
hasGroup: () => hasGroup,
|
|
37
|
+
requireGroup: () => requireGroup
|
|
34
38
|
});
|
|
35
39
|
module.exports = __toCommonJS(index_exports);
|
|
36
40
|
var import_next_auth = __toESM(require("next-auth"));
|
|
37
41
|
var import_credentials = __toESM(require("next-auth/providers/credentials"));
|
|
38
42
|
var import_cognito = __toESM(require("next-auth/providers/cognito"));
|
|
43
|
+
var AuthError = class extends Error {
|
|
44
|
+
constructor(code) {
|
|
45
|
+
super(code);
|
|
46
|
+
this.code = code;
|
|
47
|
+
this.name = "AuthError";
|
|
48
|
+
}
|
|
49
|
+
code;
|
|
50
|
+
};
|
|
51
|
+
function getUserGroups(session) {
|
|
52
|
+
return session?.user?.groups ?? [];
|
|
53
|
+
}
|
|
54
|
+
function hasGroup(session, name) {
|
|
55
|
+
if (!session) return false;
|
|
56
|
+
const target = name.toLowerCase();
|
|
57
|
+
return getUserGroups(session).some((g) => g.toLowerCase() === target);
|
|
58
|
+
}
|
|
59
|
+
function requireGroup(session, ...names) {
|
|
60
|
+
if (!session) throw new AuthError("unauthenticated");
|
|
61
|
+
if (names.length === 0) return;
|
|
62
|
+
const ok = names.some((n) => hasGroup(session, n));
|
|
63
|
+
if (!ok) throw new AuthError("forbidden");
|
|
64
|
+
}
|
|
65
|
+
function validateProdEnv(args) {
|
|
66
|
+
if (!args.isProd) return;
|
|
67
|
+
if (process.env.NEXT_PHASE === "phase-production-build") return;
|
|
68
|
+
const missing = [];
|
|
69
|
+
if (!args.secret) missing.push("AUTH_SECRET");
|
|
70
|
+
if (!args.cognitoClientId) missing.push("AUTH_COGNITO_ID");
|
|
71
|
+
if (!args.cognitoClientSecret) missing.push("AUTH_COGNITO_SECRET");
|
|
72
|
+
if (!args.cognitoIssuer) missing.push("AUTH_COGNITO_ISSUER");
|
|
73
|
+
const hasAny = !!(args.cookieDomain || args.allowedParentDomain || args.appDomain);
|
|
74
|
+
if (hasAny) {
|
|
75
|
+
if (!args.cookieDomain) missing.push("AUTH_COOKIE_DOMAIN");
|
|
76
|
+
if (!args.allowedParentDomain) missing.push("AUTH_ALLOWED_PARENT_DOMAIN");
|
|
77
|
+
if (!args.appDomain) missing.push("APP_DOMAIN");
|
|
78
|
+
}
|
|
79
|
+
if (missing.length > 0) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`[@augmenting-integrations/auth] Missing required prod env vars: ${missing.join(
|
|
82
|
+
", "
|
|
83
|
+
)}. Provide via createAuth() opts or process.env.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function buildRedirectCallback(allowedParentDomain) {
|
|
88
|
+
return ({ url, baseUrl }) => {
|
|
89
|
+
try {
|
|
90
|
+
const target = new URL(url, baseUrl);
|
|
91
|
+
if (!allowedParentDomain) {
|
|
92
|
+
return target.origin === new URL(baseUrl).origin ? target.toString() : baseUrl;
|
|
93
|
+
}
|
|
94
|
+
const apex = allowedParentDomain.replace(/^\./, "").toLowerCase();
|
|
95
|
+
const host = target.hostname.toLowerCase();
|
|
96
|
+
const ok = host === apex || host.endsWith(`.${apex}`);
|
|
97
|
+
return ok ? target.toString() : baseUrl;
|
|
98
|
+
} catch {
|
|
99
|
+
return baseUrl;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function deriveSignInPage(args) {
|
|
104
|
+
if (args.signInPage) return args.signInPage;
|
|
105
|
+
if (args.appDomain && args.allowedParentDomain) {
|
|
106
|
+
const apex = args.allowedParentDomain.replace(/^\./, "");
|
|
107
|
+
return args.appDomain === apex ? "/login" : `https://${apex}/login`;
|
|
108
|
+
}
|
|
109
|
+
return "/login";
|
|
110
|
+
}
|
|
39
111
|
function roleFromGroups(groups) {
|
|
40
112
|
if (Array.isArray(groups) && groups.length > 0) {
|
|
41
113
|
return String(groups[0]).toLowerCase();
|
|
@@ -44,12 +116,28 @@ function roleFromGroups(groups) {
|
|
|
44
116
|
}
|
|
45
117
|
function createAuth(opts) {
|
|
46
118
|
const isProd = opts.isProd ?? process.env.NODE_ENV === "production";
|
|
47
|
-
const signInPage = opts.signInPage ?? "/login";
|
|
48
119
|
const cookieDomain = isProd ? opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN : void 0;
|
|
120
|
+
const allowedParentDomain = opts.allowedParentDomain ?? process.env.AUTH_ALLOWED_PARENT_DOMAIN;
|
|
121
|
+
const appDomain = opts.appDomain ?? process.env.APP_DOMAIN;
|
|
49
122
|
const SECRET = opts.secret ?? process.env.AUTH_SECRET ?? (isProd ? void 0 : "dev-only-fallback-not-for-prod");
|
|
50
123
|
const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;
|
|
51
124
|
const cognitoClientSecret = opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;
|
|
52
125
|
const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;
|
|
126
|
+
validateProdEnv({
|
|
127
|
+
isProd,
|
|
128
|
+
cookieDomain,
|
|
129
|
+
allowedParentDomain,
|
|
130
|
+
appDomain,
|
|
131
|
+
secret: SECRET,
|
|
132
|
+
cognitoClientId,
|
|
133
|
+
cognitoClientSecret,
|
|
134
|
+
cognitoIssuer
|
|
135
|
+
});
|
|
136
|
+
const signInPage = deriveSignInPage({
|
|
137
|
+
signInPage: opts.signInPage,
|
|
138
|
+
appDomain,
|
|
139
|
+
allowedParentDomain
|
|
140
|
+
});
|
|
53
141
|
const config = {
|
|
54
142
|
secret: SECRET,
|
|
55
143
|
cookies: cookieDomain ? {
|
|
@@ -88,7 +176,8 @@ function createAuth(opts) {
|
|
|
88
176
|
id: `mock-${role}`,
|
|
89
177
|
name: `${display} (mock)`,
|
|
90
178
|
email: `${role}@example.local`,
|
|
91
|
-
role
|
|
179
|
+
role,
|
|
180
|
+
groups: [role]
|
|
92
181
|
};
|
|
93
182
|
}
|
|
94
183
|
})
|
|
@@ -99,10 +188,12 @@ function createAuth(opts) {
|
|
|
99
188
|
if (user) {
|
|
100
189
|
token.sub ??= user.id ?? void 0;
|
|
101
190
|
token.email ??= user.email ?? void 0;
|
|
102
|
-
if (!isProd
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
191
|
+
if (!isProd) {
|
|
192
|
+
const u = user;
|
|
193
|
+
const groups = u.groups ?? (u.role ? [u.role] : []);
|
|
194
|
+
if (groups.length > 0) {
|
|
195
|
+
token["cognito:groups"] = groups;
|
|
196
|
+
}
|
|
106
197
|
}
|
|
107
198
|
}
|
|
108
199
|
if (isProd && profile) {
|
|
@@ -114,9 +205,9 @@ function createAuth(opts) {
|
|
|
114
205
|
return token;
|
|
115
206
|
},
|
|
116
207
|
session: ({ session, token }) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
208
|
+
const groups = token["cognito:groups"] ?? [];
|
|
209
|
+
session.user.groups = groups;
|
|
210
|
+
session.user.role = roleFromGroups(groups);
|
|
120
211
|
return session;
|
|
121
212
|
},
|
|
122
213
|
authorized: ({ auth: session, request: { nextUrl } }) => {
|
|
@@ -126,7 +217,8 @@ function createAuth(opts) {
|
|
|
126
217
|
);
|
|
127
218
|
if (!session && isAuthedRoute) return false;
|
|
128
219
|
return true;
|
|
129
|
-
}
|
|
220
|
+
},
|
|
221
|
+
redirect: buildRedirectCallback(allowedParentDomain)
|
|
130
222
|
},
|
|
131
223
|
pages: { signIn: signInPage },
|
|
132
224
|
trustHost: true
|
|
@@ -135,6 +227,10 @@ function createAuth(opts) {
|
|
|
135
227
|
}
|
|
136
228
|
// Annotate the CommonJS export names for ESM import in node:
|
|
137
229
|
0 && (module.exports = {
|
|
138
|
-
|
|
230
|
+
AuthError,
|
|
231
|
+
createAuth,
|
|
232
|
+
getUserGroups,
|
|
233
|
+
hasGroup,
|
|
234
|
+
requireGroup
|
|
139
235
|
});
|
|
140
236
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Auth.js v5 (the package is still distributed as `next-auth`, but treat\n// these as Auth.js v5 internally — docs at https://authjs.dev, NOT\n// next-auth.js.org which is v4 and incompatible).\n//\n// Provider strategy:\n// - Production: Cognito OIDC. Group membership from `cognito:groups`\n// drives role.\n// - Dev / preview: Credentials with a role picker, BUT shaped to mirror\n// Cognito's claim payload so consumers (callbacks, gates, audit logs)\n// never branch on environment. Same `sub`, `email`, `cognito:groups`\n// in both modes.\n//\n// IMPORTANT: do not let the dev-mode session diverge from production. If\n// you add a claim in prod, also add it (with a synthetic value) in dev.\n\nimport NextAuth, { type DefaultSession, type NextAuthConfig } from \"next-auth\";\nimport Credentials from \"next-auth/providers/credentials\";\nimport Cognito from \"next-auth/providers/cognito\";\n\n// Role is intentionally `string`. Consumer apps narrow it (e.g. via local\n// module augmentation or runtime check) to whatever taxonomy their Cognito\n// user pool defines. The auth package itself makes no assumptions about\n// what roles exist.\ndeclare module \"next-auth\" {\n interface Session {\n user: {\n role: string;\n } & DefaultSession[\"user\"];\n }\n interface User {\n role: string;\n }\n}\n\nexport type CreateAuthOptions = {\n /**\n * Path prefixes that require an authenticated session.\n * Empty array = no gating (rare).\n */\n authedRoutePrefixes: string[];\n /**\n * Page to redirect to when an unauthed user hits a gated route. Can be a\n * relative path (`\"/login\"`) for in-app login, or a fully-qualified URL\n * (`\"https://example.com/login\"`) to delegate login to a peer app on a\n * sibling subdomain or the apex of a path-routed deployment.\n * Default: `/login`.\n */\n signInPage?: string;\n /**\n * Cookie domain for the session token. Set this to the parent domain\n * (e.g. `.example.com`) when running across multiple subdomains so the\n * session JWT is readable by every app sharing that parent. For\n * path-routed deployments under a single domain, leave unset (host-only\n * is correct).\n *\n * Default: read from `process.env.AUTH_COOKIE_DOMAIN`; if unset, Auth.js\n * defaults to host-only (current request hostname).\n *\n * In dev (NODE_ENV !== \"production\"), this is ignored — cookies stay\n * scoped to localhost so per-port apps don't collide.\n */\n cookieDomain?: string;\n /**\n * Override prod/dev detection. Default reads NODE_ENV.\n */\n isProd?: boolean;\n /**\n * The JWT signing secret. If not provided, falls back to\n * `process.env.AUTH_SECRET`. Pass this from a runtime fetch (e.g. AWS\n * Secrets Manager via @aws-sdk/client-secrets-manager) to keep the secret\n * out of Lambda environment variables and to support rotation without\n * redeploy (Lambda containers re-init periodically and pick up the new\n * value).\n */\n secret?: string;\n /**\n * Cognito OIDC provider config. Each field falls back to the matching\n * `AUTH_COGNITO_*` env var if not provided. Provide them explicitly to\n * fetch the client secret from Secrets Manager at runtime.\n */\n cognito?: {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n };\n};\n\nfunction roleFromGroups(groups: unknown): string {\n if (Array.isArray(groups) && groups.length > 0) {\n return String(groups[0]).toLowerCase();\n }\n return \"visitor\";\n}\n\n/**\n * Build an Auth.js v5 NextAuth() invocation. Each consuming app calls this\n * once at module scope and re-exports the returned `handlers`, `auth`,\n * `signIn`, `signOut`.\n *\n * Secrets default to env vars for simple deploys; pass them as options to\n * support runtime fetching from a secret store.\n */\nexport function createAuth(opts: CreateAuthOptions) {\n const isProd = opts.isProd ?? process.env.NODE_ENV === \"production\";\n const signInPage = opts.signInPage ?? \"/login\";\n const cookieDomain = isProd\n ? (opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN)\n : undefined;\n\n const SECRET =\n opts.secret ??\n process.env.AUTH_SECRET ??\n (isProd ? undefined : \"dev-only-fallback-not-for-prod\");\n\n const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;\n const cognitoClientSecret =\n opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;\n const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;\n\n const config: NextAuthConfig = {\n secret: SECRET,\n cookies: cookieDomain\n ? {\n sessionToken: {\n name: \"authjs.session-token\",\n options: {\n domain: cookieDomain,\n sameSite: \"lax\",\n secure: true,\n httpOnly: true,\n path: \"/\",\n },\n },\n }\n : undefined,\n providers: isProd\n ? [\n Cognito({\n clientId: cognitoClientId,\n clientSecret: cognitoClientSecret,\n issuer: cognitoIssuer,\n }),\n ]\n : [\n Credentials({\n name: \"Mock role (dev only)\",\n credentials: {\n role: {\n label: \"Role\",\n type: \"text\",\n placeholder: \"any role string\",\n },\n },\n authorize: async (credentials) => {\n const role = credentials?.role as string | undefined;\n if (!role) return null;\n const display = role.charAt(0).toUpperCase() + role.slice(1);\n return {\n id: `mock-${role}`,\n name: `${display} (mock)`,\n email: `${role}@example.local`,\n role,\n };\n },\n }),\n ],\n session: { strategy: \"jwt\" },\n callbacks: {\n jwt: ({ token, user, profile }) => {\n if (user) {\n token.sub ??= user.id ?? undefined;\n token.email ??= user.email ?? undefined;\n if (!isProd && (user as { role?: string }).role) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = [\n (user as { role: string }).role,\n ];\n }\n }\n if (isProd && profile) {\n const groups = (profile as Record<string, unknown>)[\"cognito:groups\"];\n if (groups) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = groups;\n }\n }\n return token;\n },\n session: ({ session, token }) => {\n session.user.role = roleFromGroups(\n (token as Record<string, unknown>)[\"cognito:groups\"],\n );\n return session;\n },\n authorized: ({ auth: session, request: { nextUrl } }) => {\n const path = nextUrl.pathname;\n const isAuthedRoute = opts.authedRoutePrefixes.some(\n (prefix) => path === prefix || path.startsWith(`${prefix}/`),\n );\n if (!session && isAuthedRoute) return false;\n return true;\n },\n },\n pages: { signIn: signInPage },\n trustHost: true,\n };\n\n return NextAuth(config);\n}\n\n/**\n * Re-export of NextAuthConfig for consumers that want to extend the config\n * shape returned by createAuth().\n */\nexport type { NextAuthConfig } from \"next-auth\";\n"],"mappings":"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","names":["Cognito","Credentials","NextAuth"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Auth.js v5 (the package is still distributed as `next-auth`, but treat\n// these as Auth.js v5 internally — docs at https://authjs.dev, NOT\n// next-auth.js.org which is v4 and incompatible).\n//\n// Subdomain ecosystem model:\n// - One Cognito User Pool per tenant.\n// - One Cognito App Client with ONE callback URL at the apex\n// (https://<apex>/api/auth/callback/cognito).\n// - The apex app is the auth broker. Subdomain apps redirect through it.\n// - Session cookie scoped to Domain=.<apex> so every subdomain sees it.\n// - All apps use the same createAuth() invocation; the package derives\n// the right signInPage from appDomain + allowedParentDomain.\n//\n// Provider strategy:\n// - Production: Cognito OIDC. cognito:groups drives session.user.groups.\n// - Dev / preview: Credentials with a role picker, shaped to mirror\n// Cognito's claim payload (same groups, sub, email).\n\nimport NextAuth, {\n type DefaultSession,\n type NextAuthConfig,\n type Session,\n} from \"next-auth\";\nimport Credentials from \"next-auth/providers/credentials\";\nimport Cognito from \"next-auth/providers/cognito\";\n\ndeclare module \"next-auth\" {\n interface Session {\n user: {\n groups: string[];\n role: string;\n } & DefaultSession[\"user\"];\n }\n interface User {\n role?: string;\n groups?: string[];\n }\n}\n\nexport type CreateAuthOptions = {\n /** Path prefixes that require an authenticated session. */\n authedRoutePrefixes: string[];\n /**\n * Page to redirect to when an unauthed user hits a gated route.\n * If omitted, derived automatically from appDomain + allowedParentDomain:\n * apex app gets `/login`; subdomain apps get `https://<apex>/login`.\n */\n signInPage?: string;\n /**\n * Cookie Domain attribute. In subdomain ecosystems, set to the parent\n * (e.g. `.agency.aillc.link`). Default: process.env.AUTH_COOKIE_DOMAIN.\n * In dev (NODE_ENV !== \"production\") this is ignored — cookies stay\n * host-only so per-port localhost apps don't collide.\n */\n cookieDomain?: string;\n /**\n * The parent domain that all subdomain apps share (e.g.\n * `.agency.aillc.link`). The redirect callback uses this to allow\n * post-login redirects back to any subdomain of the parent (apex or\n * `<sub>.agency.aillc.link`). Default: process.env.AUTH_ALLOWED_PARENT_DOMAIN.\n */\n allowedParentDomain?: string;\n /**\n * This app's full FQDN (e.g. `agency.aillc.link` for the apex app, or\n * `leads.agency.aillc.link` for a subdomain app). Used to derive the\n * default signInPage. Default: process.env.APP_DOMAIN.\n */\n appDomain?: string;\n /** Override prod/dev detection. Default reads NODE_ENV. */\n isProd?: boolean;\n /**\n * The JWT signing secret. Default: process.env.AUTH_SECRET.\n * In prod, pass this from a runtime fetch (Secrets Manager) to keep the\n * secret out of Lambda env vars and to support rotation without redeploy.\n */\n secret?: string;\n cognito?: {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n };\n};\n\n// ----- AuthError used by requireGroup -----\n\nexport class AuthError extends Error {\n constructor(public code: \"unauthenticated\" | \"forbidden\") {\n super(code);\n this.name = \"AuthError\";\n }\n}\n\n// ----- Group/authorization helpers -----\n\n/** Returns the user's Cognito groups (always an array, possibly empty). */\nexport function getUserGroups(session: Session | null | undefined): string[] {\n return session?.user?.groups ?? [];\n}\n\n/** Case-insensitive group membership check. */\nexport function hasGroup(session: Session | null | undefined, name: string): boolean {\n if (!session) return false;\n const target = name.toLowerCase();\n return getUserGroups(session).some((g) => g.toLowerCase() === target);\n}\n\n/**\n * Throws AuthError if no session (`unauthenticated`) or if the user is in\n * none of the provided groups (`forbidden`). Pass multiple names to allow\n * any-of.\n */\nexport function requireGroup(\n session: Session | null | undefined,\n ...names: string[]\n): void {\n if (!session) throw new AuthError(\"unauthenticated\");\n if (names.length === 0) return;\n const ok = names.some((n) => hasGroup(session, n));\n if (!ok) throw new AuthError(\"forbidden\");\n}\n\n// ----- Env validation -----\n\nfunction validateProdEnv(args: {\n isProd: boolean;\n cookieDomain: string | undefined;\n allowedParentDomain: string | undefined;\n appDomain: string | undefined;\n secret: string | undefined;\n cognitoClientId: string | undefined;\n cognitoClientSecret: string | undefined;\n cognitoIssuer: string | undefined;\n}): void {\n if (!args.isProd) return;\n // Skip during `next build` -- Cognito values come from SSM dynamic\n // refs and Secrets Manager at runtime in the deployed Lambda. Build\n // time only needs to compile route handlers; throwing here breaks\n // the build with no actionable fix (the values literally don't exist\n // yet at build time).\n if (process.env.NEXT_PHASE === \"phase-production-build\") return;\n const missing: string[] = [];\n if (!args.secret) missing.push(\"AUTH_SECRET\");\n if (!args.cognitoClientId) missing.push(\"AUTH_COGNITO_ID\");\n if (!args.cognitoClientSecret) missing.push(\"AUTH_COGNITO_SECRET\");\n if (!args.cognitoIssuer) missing.push(\"AUTH_COGNITO_ISSUER\");\n // Subdomain mode: if any of the three multi-domain values is set, all three must be.\n const hasAny = !!(args.cookieDomain || args.allowedParentDomain || args.appDomain);\n if (hasAny) {\n if (!args.cookieDomain) missing.push(\"AUTH_COOKIE_DOMAIN\");\n if (!args.allowedParentDomain) missing.push(\"AUTH_ALLOWED_PARENT_DOMAIN\");\n if (!args.appDomain) missing.push(\"APP_DOMAIN\");\n }\n if (missing.length > 0) {\n throw new Error(\n `[@augmenting-integrations/auth] Missing required prod env vars: ${missing.join(\n \", \",\n )}. Provide via createAuth() opts or process.env.`,\n );\n }\n}\n\n// ----- Redirect callback factory -----\n\nfunction buildRedirectCallback(allowedParentDomain: string | undefined) {\n return ({ url, baseUrl }: { url: string; baseUrl: string }): string => {\n try {\n const target = new URL(url, baseUrl);\n if (!allowedParentDomain) {\n return target.origin === new URL(baseUrl).origin ? target.toString() : baseUrl;\n }\n const apex = allowedParentDomain.replace(/^\\./, \"\").toLowerCase();\n const host = target.hostname.toLowerCase();\n const ok = host === apex || host.endsWith(`.${apex}`);\n return ok ? target.toString() : baseUrl;\n } catch {\n return baseUrl;\n }\n };\n}\n\n// ----- Sign-in page auto-derivation -----\n\nfunction deriveSignInPage(args: {\n signInPage: string | undefined;\n appDomain: string | undefined;\n allowedParentDomain: string | undefined;\n}): string {\n if (args.signInPage) return args.signInPage;\n if (args.appDomain && args.allowedParentDomain) {\n const apex = args.allowedParentDomain.replace(/^\\./, \"\");\n return args.appDomain === apex ? \"/login\" : `https://${apex}/login`;\n }\n return \"/login\";\n}\n\nfunction roleFromGroups(groups: unknown): string {\n if (Array.isArray(groups) && groups.length > 0) {\n return String(groups[0]).toLowerCase();\n }\n return \"visitor\";\n}\n\n// ----- Main factory -----\n\nexport function createAuth(opts: CreateAuthOptions) {\n const isProd = opts.isProd ?? process.env.NODE_ENV === \"production\";\n\n const cookieDomain = isProd\n ? (opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN)\n : undefined;\n const allowedParentDomain =\n opts.allowedParentDomain ?? process.env.AUTH_ALLOWED_PARENT_DOMAIN;\n const appDomain = opts.appDomain ?? process.env.APP_DOMAIN;\n\n const SECRET =\n opts.secret ??\n process.env.AUTH_SECRET ??\n (isProd ? undefined : \"dev-only-fallback-not-for-prod\");\n const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;\n const cognitoClientSecret =\n opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;\n const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;\n\n validateProdEnv({\n isProd,\n cookieDomain,\n allowedParentDomain,\n appDomain,\n secret: SECRET,\n cognitoClientId,\n cognitoClientSecret,\n cognitoIssuer,\n });\n\n const signInPage = deriveSignInPage({\n signInPage: opts.signInPage,\n appDomain,\n allowedParentDomain,\n });\n\n const config: NextAuthConfig = {\n secret: SECRET,\n cookies: cookieDomain\n ? {\n sessionToken: {\n name: \"authjs.session-token\",\n options: {\n domain: cookieDomain,\n sameSite: \"lax\",\n secure: true,\n httpOnly: true,\n path: \"/\",\n },\n },\n }\n : undefined,\n providers: isProd\n ? [\n Cognito({\n clientId: cognitoClientId,\n clientSecret: cognitoClientSecret,\n issuer: cognitoIssuer,\n }),\n ]\n : [\n Credentials({\n name: \"Mock role (dev only)\",\n credentials: {\n role: {\n label: \"Role\",\n type: \"text\",\n placeholder: \"any role string\",\n },\n },\n authorize: async (credentials) => {\n const role = credentials?.role as string | undefined;\n if (!role) return null;\n const display = role.charAt(0).toUpperCase() + role.slice(1);\n return {\n id: `mock-${role}`,\n name: `${display} (mock)`,\n email: `${role}@example.local`,\n role,\n groups: [role],\n };\n },\n }),\n ],\n session: { strategy: \"jwt\" },\n callbacks: {\n jwt: ({ token, user, profile }) => {\n if (user) {\n token.sub ??= user.id ?? undefined;\n token.email ??= user.email ?? undefined;\n if (!isProd) {\n const u = user as { groups?: string[]; role?: string };\n const groups = u.groups ?? (u.role ? [u.role] : []);\n if (groups.length > 0) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = groups;\n }\n }\n }\n if (isProd && profile) {\n const groups = (profile as Record<string, unknown>)[\"cognito:groups\"];\n if (groups) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = groups;\n }\n }\n return token;\n },\n session: ({ session, token }) => {\n const groups =\n ((token as Record<string, unknown>)[\"cognito:groups\"] as\n | string[]\n | undefined) ?? [];\n session.user.groups = groups;\n session.user.role = roleFromGroups(groups);\n return session;\n },\n authorized: ({ auth: session, request: { nextUrl } }) => {\n const path = nextUrl.pathname;\n const isAuthedRoute = opts.authedRoutePrefixes.some(\n (prefix) => path === prefix || path.startsWith(`${prefix}/`),\n );\n if (!session && isAuthedRoute) return false;\n return true;\n },\n redirect: buildRedirectCallback(allowedParentDomain),\n },\n pages: { signIn: signInPage },\n trustHost: true,\n };\n\n return NextAuth(config);\n}\n\nexport type { NextAuthConfig } from \"next-auth\";\n"],"mappings":"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","names":["Cognito","Credentials","NextAuth"]}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,74 @@
|
|
|
2
2
|
import NextAuth from "next-auth";
|
|
3
3
|
import Credentials from "next-auth/providers/credentials";
|
|
4
4
|
import Cognito from "next-auth/providers/cognito";
|
|
5
|
+
var AuthError = class extends Error {
|
|
6
|
+
constructor(code) {
|
|
7
|
+
super(code);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "AuthError";
|
|
10
|
+
}
|
|
11
|
+
code;
|
|
12
|
+
};
|
|
13
|
+
function getUserGroups(session) {
|
|
14
|
+
return session?.user?.groups ?? [];
|
|
15
|
+
}
|
|
16
|
+
function hasGroup(session, name) {
|
|
17
|
+
if (!session) return false;
|
|
18
|
+
const target = name.toLowerCase();
|
|
19
|
+
return getUserGroups(session).some((g) => g.toLowerCase() === target);
|
|
20
|
+
}
|
|
21
|
+
function requireGroup(session, ...names) {
|
|
22
|
+
if (!session) throw new AuthError("unauthenticated");
|
|
23
|
+
if (names.length === 0) return;
|
|
24
|
+
const ok = names.some((n) => hasGroup(session, n));
|
|
25
|
+
if (!ok) throw new AuthError("forbidden");
|
|
26
|
+
}
|
|
27
|
+
function validateProdEnv(args) {
|
|
28
|
+
if (!args.isProd) return;
|
|
29
|
+
if (process.env.NEXT_PHASE === "phase-production-build") return;
|
|
30
|
+
const missing = [];
|
|
31
|
+
if (!args.secret) missing.push("AUTH_SECRET");
|
|
32
|
+
if (!args.cognitoClientId) missing.push("AUTH_COGNITO_ID");
|
|
33
|
+
if (!args.cognitoClientSecret) missing.push("AUTH_COGNITO_SECRET");
|
|
34
|
+
if (!args.cognitoIssuer) missing.push("AUTH_COGNITO_ISSUER");
|
|
35
|
+
const hasAny = !!(args.cookieDomain || args.allowedParentDomain || args.appDomain);
|
|
36
|
+
if (hasAny) {
|
|
37
|
+
if (!args.cookieDomain) missing.push("AUTH_COOKIE_DOMAIN");
|
|
38
|
+
if (!args.allowedParentDomain) missing.push("AUTH_ALLOWED_PARENT_DOMAIN");
|
|
39
|
+
if (!args.appDomain) missing.push("APP_DOMAIN");
|
|
40
|
+
}
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`[@augmenting-integrations/auth] Missing required prod env vars: ${missing.join(
|
|
44
|
+
", "
|
|
45
|
+
)}. Provide via createAuth() opts or process.env.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function buildRedirectCallback(allowedParentDomain) {
|
|
50
|
+
return ({ url, baseUrl }) => {
|
|
51
|
+
try {
|
|
52
|
+
const target = new URL(url, baseUrl);
|
|
53
|
+
if (!allowedParentDomain) {
|
|
54
|
+
return target.origin === new URL(baseUrl).origin ? target.toString() : baseUrl;
|
|
55
|
+
}
|
|
56
|
+
const apex = allowedParentDomain.replace(/^\./, "").toLowerCase();
|
|
57
|
+
const host = target.hostname.toLowerCase();
|
|
58
|
+
const ok = host === apex || host.endsWith(`.${apex}`);
|
|
59
|
+
return ok ? target.toString() : baseUrl;
|
|
60
|
+
} catch {
|
|
61
|
+
return baseUrl;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function deriveSignInPage(args) {
|
|
66
|
+
if (args.signInPage) return args.signInPage;
|
|
67
|
+
if (args.appDomain && args.allowedParentDomain) {
|
|
68
|
+
const apex = args.allowedParentDomain.replace(/^\./, "");
|
|
69
|
+
return args.appDomain === apex ? "/login" : `https://${apex}/login`;
|
|
70
|
+
}
|
|
71
|
+
return "/login";
|
|
72
|
+
}
|
|
5
73
|
function roleFromGroups(groups) {
|
|
6
74
|
if (Array.isArray(groups) && groups.length > 0) {
|
|
7
75
|
return String(groups[0]).toLowerCase();
|
|
@@ -10,12 +78,28 @@ function roleFromGroups(groups) {
|
|
|
10
78
|
}
|
|
11
79
|
function createAuth(opts) {
|
|
12
80
|
const isProd = opts.isProd ?? process.env.NODE_ENV === "production";
|
|
13
|
-
const signInPage = opts.signInPage ?? "/login";
|
|
14
81
|
const cookieDomain = isProd ? opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN : void 0;
|
|
82
|
+
const allowedParentDomain = opts.allowedParentDomain ?? process.env.AUTH_ALLOWED_PARENT_DOMAIN;
|
|
83
|
+
const appDomain = opts.appDomain ?? process.env.APP_DOMAIN;
|
|
15
84
|
const SECRET = opts.secret ?? process.env.AUTH_SECRET ?? (isProd ? void 0 : "dev-only-fallback-not-for-prod");
|
|
16
85
|
const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;
|
|
17
86
|
const cognitoClientSecret = opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;
|
|
18
87
|
const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;
|
|
88
|
+
validateProdEnv({
|
|
89
|
+
isProd,
|
|
90
|
+
cookieDomain,
|
|
91
|
+
allowedParentDomain,
|
|
92
|
+
appDomain,
|
|
93
|
+
secret: SECRET,
|
|
94
|
+
cognitoClientId,
|
|
95
|
+
cognitoClientSecret,
|
|
96
|
+
cognitoIssuer
|
|
97
|
+
});
|
|
98
|
+
const signInPage = deriveSignInPage({
|
|
99
|
+
signInPage: opts.signInPage,
|
|
100
|
+
appDomain,
|
|
101
|
+
allowedParentDomain
|
|
102
|
+
});
|
|
19
103
|
const config = {
|
|
20
104
|
secret: SECRET,
|
|
21
105
|
cookies: cookieDomain ? {
|
|
@@ -54,7 +138,8 @@ function createAuth(opts) {
|
|
|
54
138
|
id: `mock-${role}`,
|
|
55
139
|
name: `${display} (mock)`,
|
|
56
140
|
email: `${role}@example.local`,
|
|
57
|
-
role
|
|
141
|
+
role,
|
|
142
|
+
groups: [role]
|
|
58
143
|
};
|
|
59
144
|
}
|
|
60
145
|
})
|
|
@@ -65,10 +150,12 @@ function createAuth(opts) {
|
|
|
65
150
|
if (user) {
|
|
66
151
|
token.sub ??= user.id ?? void 0;
|
|
67
152
|
token.email ??= user.email ?? void 0;
|
|
68
|
-
if (!isProd
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
153
|
+
if (!isProd) {
|
|
154
|
+
const u = user;
|
|
155
|
+
const groups = u.groups ?? (u.role ? [u.role] : []);
|
|
156
|
+
if (groups.length > 0) {
|
|
157
|
+
token["cognito:groups"] = groups;
|
|
158
|
+
}
|
|
72
159
|
}
|
|
73
160
|
}
|
|
74
161
|
if (isProd && profile) {
|
|
@@ -80,9 +167,9 @@ function createAuth(opts) {
|
|
|
80
167
|
return token;
|
|
81
168
|
},
|
|
82
169
|
session: ({ session, token }) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
);
|
|
170
|
+
const groups = token["cognito:groups"] ?? [];
|
|
171
|
+
session.user.groups = groups;
|
|
172
|
+
session.user.role = roleFromGroups(groups);
|
|
86
173
|
return session;
|
|
87
174
|
},
|
|
88
175
|
authorized: ({ auth: session, request: { nextUrl } }) => {
|
|
@@ -92,7 +179,8 @@ function createAuth(opts) {
|
|
|
92
179
|
);
|
|
93
180
|
if (!session && isAuthedRoute) return false;
|
|
94
181
|
return true;
|
|
95
|
-
}
|
|
182
|
+
},
|
|
183
|
+
redirect: buildRedirectCallback(allowedParentDomain)
|
|
96
184
|
},
|
|
97
185
|
pages: { signIn: signInPage },
|
|
98
186
|
trustHost: true
|
|
@@ -100,6 +188,10 @@ function createAuth(opts) {
|
|
|
100
188
|
return NextAuth(config);
|
|
101
189
|
}
|
|
102
190
|
export {
|
|
103
|
-
|
|
191
|
+
AuthError,
|
|
192
|
+
createAuth,
|
|
193
|
+
getUserGroups,
|
|
194
|
+
hasGroup,
|
|
195
|
+
requireGroup
|
|
104
196
|
};
|
|
105
197
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Auth.js v5 (the package is still distributed as `next-auth`, but treat\n// these as Auth.js v5 internally — docs at https://authjs.dev, NOT\n// next-auth.js.org which is v4 and incompatible).\n//\n// Provider strategy:\n// - Production: Cognito OIDC. Group membership from `cognito:groups`\n// drives role.\n// - Dev / preview: Credentials with a role picker, BUT shaped to mirror\n// Cognito's claim payload so consumers (callbacks, gates, audit logs)\n// never branch on environment. Same `sub`, `email`, `cognito:groups`\n// in both modes.\n//\n// IMPORTANT: do not let the dev-mode session diverge from production. If\n// you add a claim in prod, also add it (with a synthetic value) in dev.\n\nimport NextAuth, { type DefaultSession, type NextAuthConfig } from \"next-auth\";\nimport Credentials from \"next-auth/providers/credentials\";\nimport Cognito from \"next-auth/providers/cognito\";\n\n// Role is intentionally `string`. Consumer apps narrow it (e.g. via local\n// module augmentation or runtime check) to whatever taxonomy their Cognito\n// user pool defines. The auth package itself makes no assumptions about\n// what roles exist.\ndeclare module \"next-auth\" {\n interface Session {\n user: {\n role: string;\n } & DefaultSession[\"user\"];\n }\n interface User {\n role: string;\n }\n}\n\nexport type CreateAuthOptions = {\n /**\n * Path prefixes that require an authenticated session.\n * Empty array = no gating (rare).\n */\n authedRoutePrefixes: string[];\n /**\n * Page to redirect to when an unauthed user hits a gated route. Can be a\n * relative path (`\"/login\"`) for in-app login, or a fully-qualified URL\n * (`\"https://example.com/login\"`) to delegate login to a peer app on a\n * sibling subdomain or the apex of a path-routed deployment.\n * Default: `/login`.\n */\n signInPage?: string;\n /**\n * Cookie domain for the session token. Set this to the parent domain\n * (e.g. `.example.com`) when running across multiple subdomains so the\n * session JWT is readable by every app sharing that parent. For\n * path-routed deployments under a single domain, leave unset (host-only\n * is correct).\n *\n * Default: read from `process.env.AUTH_COOKIE_DOMAIN`; if unset, Auth.js\n * defaults to host-only (current request hostname).\n *\n * In dev (NODE_ENV !== \"production\"), this is ignored — cookies stay\n * scoped to localhost so per-port apps don't collide.\n */\n cookieDomain?: string;\n /**\n * Override prod/dev detection. Default reads NODE_ENV.\n */\n isProd?: boolean;\n /**\n * The JWT signing secret. If not provided, falls back to\n * `process.env.AUTH_SECRET`. Pass this from a runtime fetch (e.g. AWS\n * Secrets Manager via @aws-sdk/client-secrets-manager) to keep the secret\n * out of Lambda environment variables and to support rotation without\n * redeploy (Lambda containers re-init periodically and pick up the new\n * value).\n */\n secret?: string;\n /**\n * Cognito OIDC provider config. Each field falls back to the matching\n * `AUTH_COGNITO_*` env var if not provided. Provide them explicitly to\n * fetch the client secret from Secrets Manager at runtime.\n */\n cognito?: {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n };\n};\n\nfunction roleFromGroups(groups: unknown): string {\n if (Array.isArray(groups) && groups.length > 0) {\n return String(groups[0]).toLowerCase();\n }\n return \"visitor\";\n}\n\n/**\n * Build an Auth.js v5 NextAuth() invocation. Each consuming app calls this\n * once at module scope and re-exports the returned `handlers`, `auth`,\n * `signIn`, `signOut`.\n *\n * Secrets default to env vars for simple deploys; pass them as options to\n * support runtime fetching from a secret store.\n */\nexport function createAuth(opts: CreateAuthOptions) {\n const isProd = opts.isProd ?? process.env.NODE_ENV === \"production\";\n const signInPage = opts.signInPage ?? \"/login\";\n const cookieDomain = isProd\n ? (opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN)\n : undefined;\n\n const SECRET =\n opts.secret ??\n process.env.AUTH_SECRET ??\n (isProd ? undefined : \"dev-only-fallback-not-for-prod\");\n\n const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;\n const cognitoClientSecret =\n opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;\n const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;\n\n const config: NextAuthConfig = {\n secret: SECRET,\n cookies: cookieDomain\n ? {\n sessionToken: {\n name: \"authjs.session-token\",\n options: {\n domain: cookieDomain,\n sameSite: \"lax\",\n secure: true,\n httpOnly: true,\n path: \"/\",\n },\n },\n }\n : undefined,\n providers: isProd\n ? [\n Cognito({\n clientId: cognitoClientId,\n clientSecret: cognitoClientSecret,\n issuer: cognitoIssuer,\n }),\n ]\n : [\n Credentials({\n name: \"Mock role (dev only)\",\n credentials: {\n role: {\n label: \"Role\",\n type: \"text\",\n placeholder: \"any role string\",\n },\n },\n authorize: async (credentials) => {\n const role = credentials?.role as string | undefined;\n if (!role) return null;\n const display = role.charAt(0).toUpperCase() + role.slice(1);\n return {\n id: `mock-${role}`,\n name: `${display} (mock)`,\n email: `${role}@example.local`,\n role,\n };\n },\n }),\n ],\n session: { strategy: \"jwt\" },\n callbacks: {\n jwt: ({ token, user, profile }) => {\n if (user) {\n token.sub ??= user.id ?? undefined;\n token.email ??= user.email ?? undefined;\n if (!isProd && (user as { role?: string }).role) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = [\n (user as { role: string }).role,\n ];\n }\n }\n if (isProd && profile) {\n const groups = (profile as Record<string, unknown>)[\"cognito:groups\"];\n if (groups) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = groups;\n }\n }\n return token;\n },\n session: ({ session, token }) => {\n session.user.role = roleFromGroups(\n (token as Record<string, unknown>)[\"cognito:groups\"],\n );\n return session;\n },\n authorized: ({ auth: session, request: { nextUrl } }) => {\n const path = nextUrl.pathname;\n const isAuthedRoute = opts.authedRoutePrefixes.some(\n (prefix) => path === prefix || path.startsWith(`${prefix}/`),\n );\n if (!session && isAuthedRoute) return false;\n return true;\n },\n },\n pages: { signIn: signInPage },\n trustHost: true,\n };\n\n return NextAuth(config);\n}\n\n/**\n * Re-export of NextAuthConfig for consumers that want to extend the config\n * shape returned by createAuth().\n */\nexport type { NextAuthConfig } from \"next-auth\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Auth.js v5 (the package is still distributed as `next-auth`, but treat\n// these as Auth.js v5 internally — docs at https://authjs.dev, NOT\n// next-auth.js.org which is v4 and incompatible).\n//\n// Subdomain ecosystem model:\n// - One Cognito User Pool per tenant.\n// - One Cognito App Client with ONE callback URL at the apex\n// (https://<apex>/api/auth/callback/cognito).\n// - The apex app is the auth broker. Subdomain apps redirect through it.\n// - Session cookie scoped to Domain=.<apex> so every subdomain sees it.\n// - All apps use the same createAuth() invocation; the package derives\n// the right signInPage from appDomain + allowedParentDomain.\n//\n// Provider strategy:\n// - Production: Cognito OIDC. cognito:groups drives session.user.groups.\n// - Dev / preview: Credentials with a role picker, shaped to mirror\n// Cognito's claim payload (same groups, sub, email).\n\nimport NextAuth, {\n type DefaultSession,\n type NextAuthConfig,\n type Session,\n} from \"next-auth\";\nimport Credentials from \"next-auth/providers/credentials\";\nimport Cognito from \"next-auth/providers/cognito\";\n\ndeclare module \"next-auth\" {\n interface Session {\n user: {\n groups: string[];\n role: string;\n } & DefaultSession[\"user\"];\n }\n interface User {\n role?: string;\n groups?: string[];\n }\n}\n\nexport type CreateAuthOptions = {\n /** Path prefixes that require an authenticated session. */\n authedRoutePrefixes: string[];\n /**\n * Page to redirect to when an unauthed user hits a gated route.\n * If omitted, derived automatically from appDomain + allowedParentDomain:\n * apex app gets `/login`; subdomain apps get `https://<apex>/login`.\n */\n signInPage?: string;\n /**\n * Cookie Domain attribute. In subdomain ecosystems, set to the parent\n * (e.g. `.agency.aillc.link`). Default: process.env.AUTH_COOKIE_DOMAIN.\n * In dev (NODE_ENV !== \"production\") this is ignored — cookies stay\n * host-only so per-port localhost apps don't collide.\n */\n cookieDomain?: string;\n /**\n * The parent domain that all subdomain apps share (e.g.\n * `.agency.aillc.link`). The redirect callback uses this to allow\n * post-login redirects back to any subdomain of the parent (apex or\n * `<sub>.agency.aillc.link`). Default: process.env.AUTH_ALLOWED_PARENT_DOMAIN.\n */\n allowedParentDomain?: string;\n /**\n * This app's full FQDN (e.g. `agency.aillc.link` for the apex app, or\n * `leads.agency.aillc.link` for a subdomain app). Used to derive the\n * default signInPage. Default: process.env.APP_DOMAIN.\n */\n appDomain?: string;\n /** Override prod/dev detection. Default reads NODE_ENV. */\n isProd?: boolean;\n /**\n * The JWT signing secret. Default: process.env.AUTH_SECRET.\n * In prod, pass this from a runtime fetch (Secrets Manager) to keep the\n * secret out of Lambda env vars and to support rotation without redeploy.\n */\n secret?: string;\n cognito?: {\n clientId?: string;\n clientSecret?: string;\n issuer?: string;\n };\n};\n\n// ----- AuthError used by requireGroup -----\n\nexport class AuthError extends Error {\n constructor(public code: \"unauthenticated\" | \"forbidden\") {\n super(code);\n this.name = \"AuthError\";\n }\n}\n\n// ----- Group/authorization helpers -----\n\n/** Returns the user's Cognito groups (always an array, possibly empty). */\nexport function getUserGroups(session: Session | null | undefined): string[] {\n return session?.user?.groups ?? [];\n}\n\n/** Case-insensitive group membership check. */\nexport function hasGroup(session: Session | null | undefined, name: string): boolean {\n if (!session) return false;\n const target = name.toLowerCase();\n return getUserGroups(session).some((g) => g.toLowerCase() === target);\n}\n\n/**\n * Throws AuthError if no session (`unauthenticated`) or if the user is in\n * none of the provided groups (`forbidden`). Pass multiple names to allow\n * any-of.\n */\nexport function requireGroup(\n session: Session | null | undefined,\n ...names: string[]\n): void {\n if (!session) throw new AuthError(\"unauthenticated\");\n if (names.length === 0) return;\n const ok = names.some((n) => hasGroup(session, n));\n if (!ok) throw new AuthError(\"forbidden\");\n}\n\n// ----- Env validation -----\n\nfunction validateProdEnv(args: {\n isProd: boolean;\n cookieDomain: string | undefined;\n allowedParentDomain: string | undefined;\n appDomain: string | undefined;\n secret: string | undefined;\n cognitoClientId: string | undefined;\n cognitoClientSecret: string | undefined;\n cognitoIssuer: string | undefined;\n}): void {\n if (!args.isProd) return;\n // Skip during `next build` -- Cognito values come from SSM dynamic\n // refs and Secrets Manager at runtime in the deployed Lambda. Build\n // time only needs to compile route handlers; throwing here breaks\n // the build with no actionable fix (the values literally don't exist\n // yet at build time).\n if (process.env.NEXT_PHASE === \"phase-production-build\") return;\n const missing: string[] = [];\n if (!args.secret) missing.push(\"AUTH_SECRET\");\n if (!args.cognitoClientId) missing.push(\"AUTH_COGNITO_ID\");\n if (!args.cognitoClientSecret) missing.push(\"AUTH_COGNITO_SECRET\");\n if (!args.cognitoIssuer) missing.push(\"AUTH_COGNITO_ISSUER\");\n // Subdomain mode: if any of the three multi-domain values is set, all three must be.\n const hasAny = !!(args.cookieDomain || args.allowedParentDomain || args.appDomain);\n if (hasAny) {\n if (!args.cookieDomain) missing.push(\"AUTH_COOKIE_DOMAIN\");\n if (!args.allowedParentDomain) missing.push(\"AUTH_ALLOWED_PARENT_DOMAIN\");\n if (!args.appDomain) missing.push(\"APP_DOMAIN\");\n }\n if (missing.length > 0) {\n throw new Error(\n `[@augmenting-integrations/auth] Missing required prod env vars: ${missing.join(\n \", \",\n )}. Provide via createAuth() opts or process.env.`,\n );\n }\n}\n\n// ----- Redirect callback factory -----\n\nfunction buildRedirectCallback(allowedParentDomain: string | undefined) {\n return ({ url, baseUrl }: { url: string; baseUrl: string }): string => {\n try {\n const target = new URL(url, baseUrl);\n if (!allowedParentDomain) {\n return target.origin === new URL(baseUrl).origin ? target.toString() : baseUrl;\n }\n const apex = allowedParentDomain.replace(/^\\./, \"\").toLowerCase();\n const host = target.hostname.toLowerCase();\n const ok = host === apex || host.endsWith(`.${apex}`);\n return ok ? target.toString() : baseUrl;\n } catch {\n return baseUrl;\n }\n };\n}\n\n// ----- Sign-in page auto-derivation -----\n\nfunction deriveSignInPage(args: {\n signInPage: string | undefined;\n appDomain: string | undefined;\n allowedParentDomain: string | undefined;\n}): string {\n if (args.signInPage) return args.signInPage;\n if (args.appDomain && args.allowedParentDomain) {\n const apex = args.allowedParentDomain.replace(/^\\./, \"\");\n return args.appDomain === apex ? \"/login\" : `https://${apex}/login`;\n }\n return \"/login\";\n}\n\nfunction roleFromGroups(groups: unknown): string {\n if (Array.isArray(groups) && groups.length > 0) {\n return String(groups[0]).toLowerCase();\n }\n return \"visitor\";\n}\n\n// ----- Main factory -----\n\nexport function createAuth(opts: CreateAuthOptions) {\n const isProd = opts.isProd ?? process.env.NODE_ENV === \"production\";\n\n const cookieDomain = isProd\n ? (opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN)\n : undefined;\n const allowedParentDomain =\n opts.allowedParentDomain ?? process.env.AUTH_ALLOWED_PARENT_DOMAIN;\n const appDomain = opts.appDomain ?? process.env.APP_DOMAIN;\n\n const SECRET =\n opts.secret ??\n process.env.AUTH_SECRET ??\n (isProd ? undefined : \"dev-only-fallback-not-for-prod\");\n const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;\n const cognitoClientSecret =\n opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;\n const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;\n\n validateProdEnv({\n isProd,\n cookieDomain,\n allowedParentDomain,\n appDomain,\n secret: SECRET,\n cognitoClientId,\n cognitoClientSecret,\n cognitoIssuer,\n });\n\n const signInPage = deriveSignInPage({\n signInPage: opts.signInPage,\n appDomain,\n allowedParentDomain,\n });\n\n const config: NextAuthConfig = {\n secret: SECRET,\n cookies: cookieDomain\n ? {\n sessionToken: {\n name: \"authjs.session-token\",\n options: {\n domain: cookieDomain,\n sameSite: \"lax\",\n secure: true,\n httpOnly: true,\n path: \"/\",\n },\n },\n }\n : undefined,\n providers: isProd\n ? [\n Cognito({\n clientId: cognitoClientId,\n clientSecret: cognitoClientSecret,\n issuer: cognitoIssuer,\n }),\n ]\n : [\n Credentials({\n name: \"Mock role (dev only)\",\n credentials: {\n role: {\n label: \"Role\",\n type: \"text\",\n placeholder: \"any role string\",\n },\n },\n authorize: async (credentials) => {\n const role = credentials?.role as string | undefined;\n if (!role) return null;\n const display = role.charAt(0).toUpperCase() + role.slice(1);\n return {\n id: `mock-${role}`,\n name: `${display} (mock)`,\n email: `${role}@example.local`,\n role,\n groups: [role],\n };\n },\n }),\n ],\n session: { strategy: \"jwt\" },\n callbacks: {\n jwt: ({ token, user, profile }) => {\n if (user) {\n token.sub ??= user.id ?? undefined;\n token.email ??= user.email ?? undefined;\n if (!isProd) {\n const u = user as { groups?: string[]; role?: string };\n const groups = u.groups ?? (u.role ? [u.role] : []);\n if (groups.length > 0) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = groups;\n }\n }\n }\n if (isProd && profile) {\n const groups = (profile as Record<string, unknown>)[\"cognito:groups\"];\n if (groups) {\n (token as Record<string, unknown>)[\"cognito:groups\"] = groups;\n }\n }\n return token;\n },\n session: ({ session, token }) => {\n const groups =\n ((token as Record<string, unknown>)[\"cognito:groups\"] as\n | string[]\n | undefined) ?? [];\n session.user.groups = groups;\n session.user.role = roleFromGroups(groups);\n return session;\n },\n authorized: ({ auth: session, request: { nextUrl } }) => {\n const path = nextUrl.pathname;\n const isAuthedRoute = opts.authedRoutePrefixes.some(\n (prefix) => path === prefix || path.startsWith(`${prefix}/`),\n );\n if (!session && isAuthedRoute) return false;\n return true;\n },\n redirect: buildRedirectCallback(allowedParentDomain),\n },\n pages: { signIn: signInPage },\n trustHost: true,\n };\n\n return NextAuth(config);\n}\n\nexport type { NextAuthConfig } from \"next-auth\";\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@augmenting-integrations/auth",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Auth.js v5 factory: Cognito in prod, Credentials role-picker in dev. Same JWT shape (sub, email, cognito:groups) regardless of provider.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"dist",
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"clean": "rm -rf dist",
|
|
27
|
+
"test": "vitest run --passWithNoTests"
|
|
28
|
+
},
|
|
24
29
|
"peerDependencies": {
|
|
25
30
|
"next": "^16.0.0",
|
|
26
31
|
"next-auth": "^5.0.0-beta.31",
|
|
@@ -33,10 +38,5 @@
|
|
|
33
38
|
"tsup": "^8.3.5",
|
|
34
39
|
"typescript": "^5.7.2",
|
|
35
40
|
"vitest": "^4.1.5"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"clean": "rm -rf dist",
|
|
40
|
-
"test": "vitest run --passWithNoTests"
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Augmenting Integrations LLC
|
|
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.
|
package/dist/index.d.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { type DefaultSession } from "next-auth";
|
|
2
|
-
declare module "next-auth" {
|
|
3
|
-
interface Session {
|
|
4
|
-
user: {
|
|
5
|
-
role: string;
|
|
6
|
-
} & DefaultSession["user"];
|
|
7
|
-
}
|
|
8
|
-
interface User {
|
|
9
|
-
role: string;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export type CreateAuthOptions = {
|
|
13
|
-
/**
|
|
14
|
-
* Path prefixes that require an authenticated session.
|
|
15
|
-
* Empty array = no gating (rare).
|
|
16
|
-
*/
|
|
17
|
-
authedRoutePrefixes: string[];
|
|
18
|
-
/**
|
|
19
|
-
* Page to redirect to when an unauthed user hits a gated route. Can be a
|
|
20
|
-
* relative path (`"/login"`) for in-app login, or a fully-qualified URL
|
|
21
|
-
* (`"https://example.com/login"`) to delegate login to a peer app on a
|
|
22
|
-
* sibling subdomain or the apex of a path-routed deployment.
|
|
23
|
-
* Default: `/login`.
|
|
24
|
-
*/
|
|
25
|
-
signInPage?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Cookie domain for the session token. Set this to the parent domain
|
|
28
|
-
* (e.g. `.example.com`) when running across multiple subdomains so the
|
|
29
|
-
* session JWT is readable by every app sharing that parent. For
|
|
30
|
-
* path-routed deployments under a single domain, leave unset (host-only
|
|
31
|
-
* is correct).
|
|
32
|
-
*
|
|
33
|
-
* Default: read from `process.env.AUTH_COOKIE_DOMAIN`; if unset, Auth.js
|
|
34
|
-
* defaults to host-only (current request hostname).
|
|
35
|
-
*
|
|
36
|
-
* In dev (NODE_ENV !== "production"), this is ignored — cookies stay
|
|
37
|
-
* scoped to localhost so per-port apps don't collide.
|
|
38
|
-
*/
|
|
39
|
-
cookieDomain?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Override prod/dev detection. Default reads NODE_ENV.
|
|
42
|
-
*/
|
|
43
|
-
isProd?: boolean;
|
|
44
|
-
/**
|
|
45
|
-
* The JWT signing secret. If not provided, falls back to
|
|
46
|
-
* `process.env.AUTH_SECRET`. Pass this from a runtime fetch (e.g. AWS
|
|
47
|
-
* Secrets Manager via @aws-sdk/client-secrets-manager) to keep the secret
|
|
48
|
-
* out of Lambda environment variables and to support rotation without
|
|
49
|
-
* redeploy (Lambda containers re-init periodically and pick up the new
|
|
50
|
-
* value).
|
|
51
|
-
*/
|
|
52
|
-
secret?: string;
|
|
53
|
-
/**
|
|
54
|
-
* Cognito OIDC provider config. Each field falls back to the matching
|
|
55
|
-
* `AUTH_COGNITO_*` env var if not provided. Provide them explicitly to
|
|
56
|
-
* fetch the client secret from Secrets Manager at runtime.
|
|
57
|
-
*/
|
|
58
|
-
cognito?: {
|
|
59
|
-
clientId?: string;
|
|
60
|
-
clientSecret?: string;
|
|
61
|
-
issuer?: string;
|
|
62
|
-
};
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* Build an Auth.js v5 NextAuth() invocation. Each consuming app calls this
|
|
66
|
-
* once at module scope and re-exports the returned `handlers`, `auth`,
|
|
67
|
-
* `signIn`, `signOut`.
|
|
68
|
-
*
|
|
69
|
-
* Secrets default to env vars for simple deploys; pass them as options to
|
|
70
|
-
* support runtime fetching from a secret store.
|
|
71
|
-
*/
|
|
72
|
-
export declare function createAuth(opts: CreateAuthOptions): import("next-auth").NextAuthResult;
|
|
73
|
-
/**
|
|
74
|
-
* Re-export of NextAuthConfig for consumers that want to extend the config
|
|
75
|
-
* shape returned by createAuth().
|
|
76
|
-
*/
|
|
77
|
-
export type { NextAuthConfig } from "next-auth";
|
|
78
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAiB,EAAE,KAAK,cAAc,EAAuB,MAAM,WAAW,CAAC;AAQ/E,OAAO,QAAQ,WAAW,CAAC;IACzB,UAAU,OAAO;QACf,IAAI,EAAE;YACJ,IAAI,EAAE,MAAM,CAAC;SACd,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;KAC5B;IACD,UAAU,IAAI;QACZ,IAAI,EAAE,MAAM,CAAC;KACd;CACF;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AASF;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,sCAwGjD;AAED;;;GAGG;AACH,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
|