@augmenting-integrations/auth 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +107 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +39 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,12 +30,83 @@ 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
|
+
const missing = [];
|
|
68
|
+
if (!args.secret) missing.push("AUTH_SECRET");
|
|
69
|
+
if (!args.cognitoClientId) missing.push("AUTH_COGNITO_ID");
|
|
70
|
+
if (!args.cognitoClientSecret) missing.push("AUTH_COGNITO_SECRET");
|
|
71
|
+
if (!args.cognitoIssuer) missing.push("AUTH_COGNITO_ISSUER");
|
|
72
|
+
const hasAny = !!(args.cookieDomain || args.allowedParentDomain || args.appDomain);
|
|
73
|
+
if (hasAny) {
|
|
74
|
+
if (!args.cookieDomain) missing.push("AUTH_COOKIE_DOMAIN");
|
|
75
|
+
if (!args.allowedParentDomain) missing.push("AUTH_ALLOWED_PARENT_DOMAIN");
|
|
76
|
+
if (!args.appDomain) missing.push("APP_DOMAIN");
|
|
77
|
+
}
|
|
78
|
+
if (missing.length > 0) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`[@augmenting-integrations/auth] Missing required prod env vars: ${missing.join(
|
|
81
|
+
", "
|
|
82
|
+
)}. Provide via createAuth() opts or process.env.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function buildRedirectCallback(allowedParentDomain) {
|
|
87
|
+
return ({ url, baseUrl }) => {
|
|
88
|
+
try {
|
|
89
|
+
const target = new URL(url, baseUrl);
|
|
90
|
+
if (!allowedParentDomain) {
|
|
91
|
+
return target.origin === new URL(baseUrl).origin ? target.toString() : baseUrl;
|
|
92
|
+
}
|
|
93
|
+
const apex = allowedParentDomain.replace(/^\./, "").toLowerCase();
|
|
94
|
+
const host = target.hostname.toLowerCase();
|
|
95
|
+
const ok = host === apex || host.endsWith(`.${apex}`);
|
|
96
|
+
return ok ? target.toString() : baseUrl;
|
|
97
|
+
} catch {
|
|
98
|
+
return baseUrl;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function deriveSignInPage(args) {
|
|
103
|
+
if (args.signInPage) return args.signInPage;
|
|
104
|
+
if (args.appDomain && args.allowedParentDomain) {
|
|
105
|
+
const apex = args.allowedParentDomain.replace(/^\./, "");
|
|
106
|
+
return args.appDomain === apex ? "/login" : `https://${apex}/login`;
|
|
107
|
+
}
|
|
108
|
+
return "/login";
|
|
109
|
+
}
|
|
39
110
|
function roleFromGroups(groups) {
|
|
40
111
|
if (Array.isArray(groups) && groups.length > 0) {
|
|
41
112
|
return String(groups[0]).toLowerCase();
|
|
@@ -44,12 +115,28 @@ function roleFromGroups(groups) {
|
|
|
44
115
|
}
|
|
45
116
|
function createAuth(opts) {
|
|
46
117
|
const isProd = opts.isProd ?? process.env.NODE_ENV === "production";
|
|
47
|
-
const signInPage = opts.signInPage ?? "/login";
|
|
48
118
|
const cookieDomain = isProd ? opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN : void 0;
|
|
119
|
+
const allowedParentDomain = opts.allowedParentDomain ?? process.env.AUTH_ALLOWED_PARENT_DOMAIN;
|
|
120
|
+
const appDomain = opts.appDomain ?? process.env.APP_DOMAIN;
|
|
49
121
|
const SECRET = opts.secret ?? process.env.AUTH_SECRET ?? (isProd ? void 0 : "dev-only-fallback-not-for-prod");
|
|
50
122
|
const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;
|
|
51
123
|
const cognitoClientSecret = opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;
|
|
52
124
|
const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;
|
|
125
|
+
validateProdEnv({
|
|
126
|
+
isProd,
|
|
127
|
+
cookieDomain,
|
|
128
|
+
allowedParentDomain,
|
|
129
|
+
appDomain,
|
|
130
|
+
secret: SECRET,
|
|
131
|
+
cognitoClientId,
|
|
132
|
+
cognitoClientSecret,
|
|
133
|
+
cognitoIssuer
|
|
134
|
+
});
|
|
135
|
+
const signInPage = deriveSignInPage({
|
|
136
|
+
signInPage: opts.signInPage,
|
|
137
|
+
appDomain,
|
|
138
|
+
allowedParentDomain
|
|
139
|
+
});
|
|
53
140
|
const config = {
|
|
54
141
|
secret: SECRET,
|
|
55
142
|
cookies: cookieDomain ? {
|
|
@@ -88,7 +175,8 @@ function createAuth(opts) {
|
|
|
88
175
|
id: `mock-${role}`,
|
|
89
176
|
name: `${display} (mock)`,
|
|
90
177
|
email: `${role}@example.local`,
|
|
91
|
-
role
|
|
178
|
+
role,
|
|
179
|
+
groups: [role]
|
|
92
180
|
};
|
|
93
181
|
}
|
|
94
182
|
})
|
|
@@ -99,10 +187,12 @@ function createAuth(opts) {
|
|
|
99
187
|
if (user) {
|
|
100
188
|
token.sub ??= user.id ?? void 0;
|
|
101
189
|
token.email ??= user.email ?? void 0;
|
|
102
|
-
if (!isProd
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
190
|
+
if (!isProd) {
|
|
191
|
+
const u = user;
|
|
192
|
+
const groups = u.groups ?? (u.role ? [u.role] : []);
|
|
193
|
+
if (groups.length > 0) {
|
|
194
|
+
token["cognito:groups"] = groups;
|
|
195
|
+
}
|
|
106
196
|
}
|
|
107
197
|
}
|
|
108
198
|
if (isProd && profile) {
|
|
@@ -114,9 +204,9 @@ function createAuth(opts) {
|
|
|
114
204
|
return token;
|
|
115
205
|
},
|
|
116
206
|
session: ({ session, token }) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
207
|
+
const groups = token["cognito:groups"] ?? [];
|
|
208
|
+
session.user.groups = groups;
|
|
209
|
+
session.user.role = roleFromGroups(groups);
|
|
120
210
|
return session;
|
|
121
211
|
},
|
|
122
212
|
authorized: ({ auth: session, request: { nextUrl } }) => {
|
|
@@ -126,7 +216,8 @@ function createAuth(opts) {
|
|
|
126
216
|
);
|
|
127
217
|
if (!session && isAuthedRoute) return false;
|
|
128
218
|
return true;
|
|
129
|
-
}
|
|
219
|
+
},
|
|
220
|
+
redirect: buildRedirectCallback(allowedParentDomain)
|
|
130
221
|
},
|
|
131
222
|
pages: { signIn: signInPage },
|
|
132
223
|
trustHost: true
|
|
@@ -135,6 +226,10 @@ function createAuth(opts) {
|
|
|
135
226
|
}
|
|
136
227
|
// Annotate the CommonJS export names for ESM import in node:
|
|
137
228
|
0 && (module.exports = {
|
|
138
|
-
|
|
229
|
+
AuthError,
|
|
230
|
+
createAuth,
|
|
231
|
+
getUserGroups,
|
|
232
|
+
hasGroup,
|
|
233
|
+
requireGroup
|
|
139
234
|
});
|
|
140
235
|
//# 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 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.d.ts
CHANGED
|
@@ -1,78 +1,73 @@
|
|
|
1
|
-
import { type DefaultSession } from "next-auth";
|
|
1
|
+
import { type DefaultSession, type Session } from "next-auth";
|
|
2
2
|
declare module "next-auth" {
|
|
3
3
|
interface Session {
|
|
4
4
|
user: {
|
|
5
|
+
groups: string[];
|
|
5
6
|
role: string;
|
|
6
7
|
} & DefaultSession["user"];
|
|
7
8
|
}
|
|
8
9
|
interface User {
|
|
9
|
-
role
|
|
10
|
+
role?: string;
|
|
11
|
+
groups?: string[];
|
|
10
12
|
}
|
|
11
13
|
}
|
|
12
14
|
export type CreateAuthOptions = {
|
|
13
|
-
/**
|
|
14
|
-
* Path prefixes that require an authenticated session.
|
|
15
|
-
* Empty array = no gating (rare).
|
|
16
|
-
*/
|
|
15
|
+
/** Path prefixes that require an authenticated session. */
|
|
17
16
|
authedRoutePrefixes: string[];
|
|
18
17
|
/**
|
|
19
|
-
* Page to redirect to when an unauthed user hits a gated route.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* sibling subdomain or the apex of a path-routed deployment.
|
|
23
|
-
* Default: `/login`.
|
|
18
|
+
* Page to redirect to when an unauthed user hits a gated route.
|
|
19
|
+
* If omitted, derived automatically from appDomain + allowedParentDomain:
|
|
20
|
+
* apex app gets `/login`; subdomain apps get `https://<apex>/login`.
|
|
24
21
|
*/
|
|
25
22
|
signInPage?: string;
|
|
26
23
|
/**
|
|
27
|
-
* Cookie
|
|
28
|
-
* (e.g. `.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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.
|
|
24
|
+
* Cookie Domain attribute. In subdomain ecosystems, set to the parent
|
|
25
|
+
* (e.g. `.agency.aillc.link`). Default: process.env.AUTH_COOKIE_DOMAIN.
|
|
26
|
+
* In dev (NODE_ENV !== "production") this is ignored — cookies stay
|
|
27
|
+
* host-only so per-port localhost apps don't collide.
|
|
38
28
|
*/
|
|
39
29
|
cookieDomain?: string;
|
|
40
30
|
/**
|
|
41
|
-
*
|
|
31
|
+
* The parent domain that all subdomain apps share (e.g.
|
|
32
|
+
* `.agency.aillc.link`). The redirect callback uses this to allow
|
|
33
|
+
* post-login redirects back to any subdomain of the parent (apex or
|
|
34
|
+
* `<sub>.agency.aillc.link`). Default: process.env.AUTH_ALLOWED_PARENT_DOMAIN.
|
|
42
35
|
*/
|
|
43
|
-
|
|
36
|
+
allowedParentDomain?: string;
|
|
44
37
|
/**
|
|
45
|
-
*
|
|
46
|
-
* `
|
|
47
|
-
*
|
|
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).
|
|
38
|
+
* This app's full FQDN (e.g. `agency.aillc.link` for the apex app, or
|
|
39
|
+
* `leads.agency.aillc.link` for a subdomain app). Used to derive the
|
|
40
|
+
* default signInPage. Default: process.env.APP_DOMAIN.
|
|
51
41
|
*/
|
|
52
|
-
|
|
42
|
+
appDomain?: string;
|
|
43
|
+
/** Override prod/dev detection. Default reads NODE_ENV. */
|
|
44
|
+
isProd?: boolean;
|
|
53
45
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
46
|
+
* The JWT signing secret. Default: process.env.AUTH_SECRET.
|
|
47
|
+
* In prod, pass this from a runtime fetch (Secrets Manager) to keep the
|
|
48
|
+
* secret out of Lambda env vars and to support rotation without redeploy.
|
|
57
49
|
*/
|
|
50
|
+
secret?: string;
|
|
58
51
|
cognito?: {
|
|
59
52
|
clientId?: string;
|
|
60
53
|
clientSecret?: string;
|
|
61
54
|
issuer?: string;
|
|
62
55
|
};
|
|
63
56
|
};
|
|
57
|
+
export declare class AuthError extends Error {
|
|
58
|
+
code: "unauthenticated" | "forbidden";
|
|
59
|
+
constructor(code: "unauthenticated" | "forbidden");
|
|
60
|
+
}
|
|
61
|
+
/** Returns the user's Cognito groups (always an array, possibly empty). */
|
|
62
|
+
export declare function getUserGroups(session: Session | null | undefined): string[];
|
|
63
|
+
/** Case-insensitive group membership check. */
|
|
64
|
+
export declare function hasGroup(session: Session | null | undefined, name: string): boolean;
|
|
64
65
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* Secrets default to env vars for simple deploys; pass them as options to
|
|
70
|
-
* support runtime fetching from a secret store.
|
|
66
|
+
* Throws AuthError if no session (`unauthenticated`) or if the user is in
|
|
67
|
+
* none of the provided groups (`forbidden`). Pass multiple names to allow
|
|
68
|
+
* any-of.
|
|
71
69
|
*/
|
|
70
|
+
export declare function requireGroup(session: Session | null | undefined, ...names: string[]): void;
|
|
72
71
|
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
72
|
export type { NextAuthConfig } from "next-auth";
|
|
78
73
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAiB,EACf,KAAK,cAAc,EAEnB,KAAK,OAAO,EACb,MAAM,WAAW,CAAC;AAInB,OAAO,QAAQ,WAAW,CAAC;IACzB,UAAU,OAAO;QACf,IAAI,EAAE;YACJ,MAAM,EAAE,MAAM,EAAE,CAAC;YACjB,IAAI,EAAE,MAAM,CAAC;SACd,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;KAC5B;IACD,UAAU,IAAI;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB;CACF;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,2DAA2D;IAC3D,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,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;AAIF,qBAAa,SAAU,SAAQ,KAAK;IACf,IAAI,EAAE,iBAAiB,GAAG,WAAW;gBAArC,IAAI,EAAE,iBAAiB,GAAG,WAAW;CAIzD;AAID,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAE3E;AAED,+CAA+C;AAC/C,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAInF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,EACnC,GAAG,KAAK,EAAE,MAAM,EAAE,GACjB,IAAI,CAKN;AA+ED,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,sCAkIjD;AAED,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,73 @@
|
|
|
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
|
+
const missing = [];
|
|
30
|
+
if (!args.secret) missing.push("AUTH_SECRET");
|
|
31
|
+
if (!args.cognitoClientId) missing.push("AUTH_COGNITO_ID");
|
|
32
|
+
if (!args.cognitoClientSecret) missing.push("AUTH_COGNITO_SECRET");
|
|
33
|
+
if (!args.cognitoIssuer) missing.push("AUTH_COGNITO_ISSUER");
|
|
34
|
+
const hasAny = !!(args.cookieDomain || args.allowedParentDomain || args.appDomain);
|
|
35
|
+
if (hasAny) {
|
|
36
|
+
if (!args.cookieDomain) missing.push("AUTH_COOKIE_DOMAIN");
|
|
37
|
+
if (!args.allowedParentDomain) missing.push("AUTH_ALLOWED_PARENT_DOMAIN");
|
|
38
|
+
if (!args.appDomain) missing.push("APP_DOMAIN");
|
|
39
|
+
}
|
|
40
|
+
if (missing.length > 0) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`[@augmenting-integrations/auth] Missing required prod env vars: ${missing.join(
|
|
43
|
+
", "
|
|
44
|
+
)}. Provide via createAuth() opts or process.env.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function buildRedirectCallback(allowedParentDomain) {
|
|
49
|
+
return ({ url, baseUrl }) => {
|
|
50
|
+
try {
|
|
51
|
+
const target = new URL(url, baseUrl);
|
|
52
|
+
if (!allowedParentDomain) {
|
|
53
|
+
return target.origin === new URL(baseUrl).origin ? target.toString() : baseUrl;
|
|
54
|
+
}
|
|
55
|
+
const apex = allowedParentDomain.replace(/^\./, "").toLowerCase();
|
|
56
|
+
const host = target.hostname.toLowerCase();
|
|
57
|
+
const ok = host === apex || host.endsWith(`.${apex}`);
|
|
58
|
+
return ok ? target.toString() : baseUrl;
|
|
59
|
+
} catch {
|
|
60
|
+
return baseUrl;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function deriveSignInPage(args) {
|
|
65
|
+
if (args.signInPage) return args.signInPage;
|
|
66
|
+
if (args.appDomain && args.allowedParentDomain) {
|
|
67
|
+
const apex = args.allowedParentDomain.replace(/^\./, "");
|
|
68
|
+
return args.appDomain === apex ? "/login" : `https://${apex}/login`;
|
|
69
|
+
}
|
|
70
|
+
return "/login";
|
|
71
|
+
}
|
|
5
72
|
function roleFromGroups(groups) {
|
|
6
73
|
if (Array.isArray(groups) && groups.length > 0) {
|
|
7
74
|
return String(groups[0]).toLowerCase();
|
|
@@ -10,12 +77,28 @@ function roleFromGroups(groups) {
|
|
|
10
77
|
}
|
|
11
78
|
function createAuth(opts) {
|
|
12
79
|
const isProd = opts.isProd ?? process.env.NODE_ENV === "production";
|
|
13
|
-
const signInPage = opts.signInPage ?? "/login";
|
|
14
80
|
const cookieDomain = isProd ? opts.cookieDomain ?? process.env.AUTH_COOKIE_DOMAIN : void 0;
|
|
81
|
+
const allowedParentDomain = opts.allowedParentDomain ?? process.env.AUTH_ALLOWED_PARENT_DOMAIN;
|
|
82
|
+
const appDomain = opts.appDomain ?? process.env.APP_DOMAIN;
|
|
15
83
|
const SECRET = opts.secret ?? process.env.AUTH_SECRET ?? (isProd ? void 0 : "dev-only-fallback-not-for-prod");
|
|
16
84
|
const cognitoClientId = opts.cognito?.clientId ?? process.env.AUTH_COGNITO_ID;
|
|
17
85
|
const cognitoClientSecret = opts.cognito?.clientSecret ?? process.env.AUTH_COGNITO_SECRET;
|
|
18
86
|
const cognitoIssuer = opts.cognito?.issuer ?? process.env.AUTH_COGNITO_ISSUER;
|
|
87
|
+
validateProdEnv({
|
|
88
|
+
isProd,
|
|
89
|
+
cookieDomain,
|
|
90
|
+
allowedParentDomain,
|
|
91
|
+
appDomain,
|
|
92
|
+
secret: SECRET,
|
|
93
|
+
cognitoClientId,
|
|
94
|
+
cognitoClientSecret,
|
|
95
|
+
cognitoIssuer
|
|
96
|
+
});
|
|
97
|
+
const signInPage = deriveSignInPage({
|
|
98
|
+
signInPage: opts.signInPage,
|
|
99
|
+
appDomain,
|
|
100
|
+
allowedParentDomain
|
|
101
|
+
});
|
|
19
102
|
const config = {
|
|
20
103
|
secret: SECRET,
|
|
21
104
|
cookies: cookieDomain ? {
|
|
@@ -54,7 +137,8 @@ function createAuth(opts) {
|
|
|
54
137
|
id: `mock-${role}`,
|
|
55
138
|
name: `${display} (mock)`,
|
|
56
139
|
email: `${role}@example.local`,
|
|
57
|
-
role
|
|
140
|
+
role,
|
|
141
|
+
groups: [role]
|
|
58
142
|
};
|
|
59
143
|
}
|
|
60
144
|
})
|
|
@@ -65,10 +149,12 @@ function createAuth(opts) {
|
|
|
65
149
|
if (user) {
|
|
66
150
|
token.sub ??= user.id ?? void 0;
|
|
67
151
|
token.email ??= user.email ?? void 0;
|
|
68
|
-
if (!isProd
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
152
|
+
if (!isProd) {
|
|
153
|
+
const u = user;
|
|
154
|
+
const groups = u.groups ?? (u.role ? [u.role] : []);
|
|
155
|
+
if (groups.length > 0) {
|
|
156
|
+
token["cognito:groups"] = groups;
|
|
157
|
+
}
|
|
72
158
|
}
|
|
73
159
|
}
|
|
74
160
|
if (isProd && profile) {
|
|
@@ -80,9 +166,9 @@ function createAuth(opts) {
|
|
|
80
166
|
return token;
|
|
81
167
|
},
|
|
82
168
|
session: ({ session, token }) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
);
|
|
169
|
+
const groups = token["cognito:groups"] ?? [];
|
|
170
|
+
session.user.groups = groups;
|
|
171
|
+
session.user.role = roleFromGroups(groups);
|
|
86
172
|
return session;
|
|
87
173
|
},
|
|
88
174
|
authorized: ({ auth: session, request: { nextUrl } }) => {
|
|
@@ -92,7 +178,8 @@ function createAuth(opts) {
|
|
|
92
178
|
);
|
|
93
179
|
if (!session && isAuthedRoute) return false;
|
|
94
180
|
return true;
|
|
95
|
-
}
|
|
181
|
+
},
|
|
182
|
+
redirect: buildRedirectCallback(allowedParentDomain)
|
|
96
183
|
},
|
|
97
184
|
pages: { signIn: signInPage },
|
|
98
185
|
trustHost: true
|
|
@@ -100,6 +187,10 @@ function createAuth(opts) {
|
|
|
100
187
|
return NextAuth(config);
|
|
101
188
|
}
|
|
102
189
|
export {
|
|
103
|
-
|
|
190
|
+
AuthError,
|
|
191
|
+
createAuth,
|
|
192
|
+
getUserGroups,
|
|
193
|
+
hasGroup,
|
|
194
|
+
requireGroup
|
|
104
195
|
};
|
|
105
196
|
//# 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 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.0
|
|
3
|
+
"version": "4.1.0",
|
|
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": {
|