@securitydept/basic-auth-context-client 0.2.0-beta.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/client-DU98lpa_.d.mts +91 -0
- package/dist/client-D_0eAARH.mjs +136 -0
- package/dist/client-D_0eAARH.mjs.map +1 -0
- package/dist/index.d.mts +94 -0
- package/dist/index.mjs +3 -0
- package/dist/server/index.d.mts +73 -0
- package/dist/server/index.mjs +47 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/types-2IoKrgnl.mjs +20 -0
- package/dist/types-2IoKrgnl.mjs.map +1 -0
- package/dist/web/index.d.mts +45 -0
- package/dist/web/index.mjs +37 -0
- package/dist/web/index.mjs.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/** Configuration for a single Basic Auth zone. */
|
|
3
|
+
interface BasicAuthZoneConfig {
|
|
4
|
+
/** URL path prefix for this zone (e.g. "/basic"). */
|
|
5
|
+
zonePrefix: string;
|
|
6
|
+
/** Login subpath relative to zone prefix (default: "/login"). */
|
|
7
|
+
loginSubpath?: string;
|
|
8
|
+
/** Logout subpath relative to zone prefix (default: "/logout"). */
|
|
9
|
+
logoutSubpath?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Configuration for the Basic Auth Context Client. */
|
|
12
|
+
interface BasicAuthContextClientConfig {
|
|
13
|
+
/** Base URL of the SecurityDept server. */
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
/** One or more Basic Auth zones to manage. */
|
|
16
|
+
zones: BasicAuthZoneConfig[];
|
|
17
|
+
/** Name of the query parameter for post-auth redirect (default: "post_auth_redirect_uri"). */
|
|
18
|
+
postAuthRedirectParam?: string;
|
|
19
|
+
}
|
|
20
|
+
declare const BasicAuthBoundaryKind: {
|
|
21
|
+
readonly Authenticated: "authenticated";
|
|
22
|
+
readonly Challenge: "challenge";
|
|
23
|
+
readonly Unauthorized: "unauthorized";
|
|
24
|
+
readonly LogoutPoison: "logout_poison";
|
|
25
|
+
};
|
|
26
|
+
type BasicAuthBoundaryKind = (typeof BasicAuthBoundaryKind)[keyof typeof BasicAuthBoundaryKind];
|
|
27
|
+
interface BasicAuthBoundaryObservation {
|
|
28
|
+
status: number;
|
|
29
|
+
challengeHeader?: string | null;
|
|
30
|
+
requestPath?: string;
|
|
31
|
+
}
|
|
32
|
+
declare const AuthGuardResultKind: {
|
|
33
|
+
readonly Ok: "ok";
|
|
34
|
+
readonly Redirect: "redirect";
|
|
35
|
+
};
|
|
36
|
+
type AuthGuardResultKind = (typeof AuthGuardResultKind)[keyof typeof AuthGuardResultKind];
|
|
37
|
+
declare const AuthGuardRedirectStatus: {
|
|
38
|
+
readonly Found: 302;
|
|
39
|
+
readonly SeeOther: 303;
|
|
40
|
+
readonly TemporaryRedirect: 307;
|
|
41
|
+
};
|
|
42
|
+
type AuthGuardRedirectStatus = (typeof AuthGuardRedirectStatus)[keyof typeof AuthGuardRedirectStatus];
|
|
43
|
+
/** Redirect instruction — framework-neutral result. */
|
|
44
|
+
type AuthGuardResult<T> = {
|
|
45
|
+
kind: typeof AuthGuardResultKind.Ok;
|
|
46
|
+
value: T;
|
|
47
|
+
} | {
|
|
48
|
+
kind: typeof AuthGuardResultKind.Redirect;
|
|
49
|
+
status: AuthGuardRedirectStatus;
|
|
50
|
+
location: string;
|
|
51
|
+
};
|
|
52
|
+
/** Resolved zone with computed paths. */
|
|
53
|
+
interface ResolvedBasicAuthZone {
|
|
54
|
+
zonePrefix: string;
|
|
55
|
+
loginPath: string;
|
|
56
|
+
logoutPath: string;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/client.d.ts
|
|
60
|
+
declare function readBasicAuthBoundaryKind(options: BasicAuthBoundaryObservation): BasicAuthBoundaryKind;
|
|
61
|
+
/**
|
|
62
|
+
* Basic Auth Context Client.
|
|
63
|
+
*
|
|
64
|
+
* Thin client that manages zone boundary awareness and redirect instructions.
|
|
65
|
+
* It does NOT manage credentials or HTTP auth headers — those are handled
|
|
66
|
+
* by the browser's native Basic Auth UI triggered by 401 + WWW-Authenticate.
|
|
67
|
+
*/
|
|
68
|
+
declare class BasicAuthContextClient {
|
|
69
|
+
readonly zones: readonly ResolvedBasicAuthZone[];
|
|
70
|
+
private readonly _baseUrl;
|
|
71
|
+
private readonly _postAuthRedirectParam;
|
|
72
|
+
constructor(config: BasicAuthContextClientConfig);
|
|
73
|
+
/** Find the zone that contains the given path. */
|
|
74
|
+
zoneForPath(path: string): ResolvedBasicAuthZone | undefined;
|
|
75
|
+
zoneForPrefix(zonePrefix: string): ResolvedBasicAuthZone | undefined;
|
|
76
|
+
/** Check whether a path falls inside any configured zone. */
|
|
77
|
+
isInZone(path: string): boolean;
|
|
78
|
+
/** Build the full login URL for a zone, optionally with a post-auth redirect. */
|
|
79
|
+
loginUrl(zone: ResolvedBasicAuthZone, postAuthRedirectUri?: string): string;
|
|
80
|
+
loginUrlForZonePrefix(zonePrefix: string, postAuthRedirectUri?: string): string | null;
|
|
81
|
+
/** Build the full logout URL for a zone. */
|
|
82
|
+
logoutUrl(zone: ResolvedBasicAuthZone): string;
|
|
83
|
+
/**
|
|
84
|
+
* Handle a 401 response: if the current path is inside a zone,
|
|
85
|
+
* return a redirect instruction to the zone's login URL.
|
|
86
|
+
*/
|
|
87
|
+
handleUnauthorized(currentPath: string, responseStatus: number): AuthGuardResult<null>;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { AuthGuardResultKind as a, BasicAuthContextClientConfig as c, AuthGuardResult as i, BasicAuthZoneConfig as l, readBasicAuthBoundaryKind as n, BasicAuthBoundaryKind as o, AuthGuardRedirectStatus as r, BasicAuthBoundaryObservation as s, BasicAuthContextClient as t, ResolvedBasicAuthZone as u };
|
|
91
|
+
//# sourceMappingURL=client-DU98lpa_.d.mts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { n as AuthGuardResultKind, r as BasicAuthBoundaryKind, t as AuthGuardRedirectStatus } from "./types-2IoKrgnl.mjs";
|
|
2
|
+
import { createSchema, validateWithSchemaSync } from "@securitydept/client";
|
|
3
|
+
//#region src/schemas.ts
|
|
4
|
+
function isNonEmptyString(v) {
|
|
5
|
+
return typeof v === "string" && v.length > 0;
|
|
6
|
+
}
|
|
7
|
+
function isObject(v) {
|
|
8
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
9
|
+
}
|
|
10
|
+
function validateZoneConfig(input, index) {
|
|
11
|
+
const issues = [];
|
|
12
|
+
if (!isObject(input)) {
|
|
13
|
+
issues.push({ message: `zones[${index}]: expected an object` });
|
|
14
|
+
return { issues };
|
|
15
|
+
}
|
|
16
|
+
if (!isNonEmptyString(input.zonePrefix)) issues.push({ message: `zones[${index}].zonePrefix: expected a non-empty string` });
|
|
17
|
+
if (input.loginSubpath !== void 0 && typeof input.loginSubpath !== "string") issues.push({ message: `zones[${index}].loginSubpath: expected a string if provided` });
|
|
18
|
+
if (input.logoutSubpath !== void 0 && typeof input.logoutSubpath !== "string") issues.push({ message: `zones[${index}].logoutSubpath: expected a string if provided` });
|
|
19
|
+
return issues.length > 0 ? { issues } : null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Schema for validating BasicAuthContextClientConfig.
|
|
23
|
+
*
|
|
24
|
+
* Validates:
|
|
25
|
+
* - `baseUrl` is a non-empty string
|
|
26
|
+
* - `zones` is a non-empty array of valid zone configs
|
|
27
|
+
* - each zone has a non-empty `zonePrefix`
|
|
28
|
+
* - optional fields have correct types when provided
|
|
29
|
+
*/
|
|
30
|
+
const BasicAuthContextClientConfigSchema = createSchema({ validate(input) {
|
|
31
|
+
const issues = [];
|
|
32
|
+
if (!isObject(input)) return { issues: [{ message: "Expected an object" }] };
|
|
33
|
+
if (!isNonEmptyString(input.baseUrl)) issues.push({ message: "baseUrl: expected a non-empty string" });
|
|
34
|
+
if (!Array.isArray(input.zones) || input.zones.length === 0) issues.push({ message: "zones: expected a non-empty array of zone configs" });
|
|
35
|
+
else for (let i = 0; i < input.zones.length; i++) {
|
|
36
|
+
const zoneResult = validateZoneConfig(input.zones[i], i);
|
|
37
|
+
if (zoneResult) issues.push(...zoneResult.issues);
|
|
38
|
+
}
|
|
39
|
+
if (input.postAuthRedirectParam !== void 0 && typeof input.postAuthRedirectParam !== "string") issues.push({ message: "postAuthRedirectParam: expected a string if provided" });
|
|
40
|
+
if (issues.length > 0) return { issues };
|
|
41
|
+
return { value: input };
|
|
42
|
+
} });
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/client.ts
|
|
45
|
+
const DEFAULT_LOGIN_SUBPATH = "/login";
|
|
46
|
+
const DEFAULT_LOGOUT_SUBPATH = "/logout";
|
|
47
|
+
const DEFAULT_POST_AUTH_REDIRECT_PARAM = "post_auth_redirect_uri";
|
|
48
|
+
function resolveZone(config) {
|
|
49
|
+
const prefix = config.zonePrefix.replace(/\/+$/, "");
|
|
50
|
+
const loginSub = config.loginSubpath ?? DEFAULT_LOGIN_SUBPATH;
|
|
51
|
+
const logoutSub = config.logoutSubpath ?? DEFAULT_LOGOUT_SUBPATH;
|
|
52
|
+
return {
|
|
53
|
+
zonePrefix: prefix,
|
|
54
|
+
loginPath: prefix + loginSub,
|
|
55
|
+
logoutPath: prefix + logoutSub
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function readBasicAuthBoundaryKind(options) {
|
|
59
|
+
if (options.status < 400) return BasicAuthBoundaryKind.Authenticated;
|
|
60
|
+
if (options.challengeHeader) return BasicAuthBoundaryKind.Challenge;
|
|
61
|
+
if (options.status === 401 && options.requestPath === "/basic/logout") return BasicAuthBoundaryKind.LogoutPoison;
|
|
62
|
+
return BasicAuthBoundaryKind.Unauthorized;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Basic Auth Context Client.
|
|
66
|
+
*
|
|
67
|
+
* Thin client that manages zone boundary awareness and redirect instructions.
|
|
68
|
+
* It does NOT manage credentials or HTTP auth headers — those are handled
|
|
69
|
+
* by the browser's native Basic Auth UI triggered by 401 + WWW-Authenticate.
|
|
70
|
+
*/
|
|
71
|
+
var BasicAuthContextClient = class {
|
|
72
|
+
zones;
|
|
73
|
+
_baseUrl;
|
|
74
|
+
_postAuthRedirectParam;
|
|
75
|
+
constructor(config) {
|
|
76
|
+
const result = validateWithSchemaSync(BasicAuthContextClientConfigSchema, config);
|
|
77
|
+
if (!result.success) console.warn(`[BasicAuthContextClient] Deprecated config shape detected (will become an error in a future release): ${result.issues.map((i) => i.message).join("; ")}`);
|
|
78
|
+
this._baseUrl = (config.baseUrl ?? "").replace(/\/+$/, "");
|
|
79
|
+
this._postAuthRedirectParam = config.postAuthRedirectParam ?? DEFAULT_POST_AUTH_REDIRECT_PARAM;
|
|
80
|
+
this.zones = (config.zones ?? []).map(resolveZone);
|
|
81
|
+
}
|
|
82
|
+
/** Find the zone that contains the given path. */
|
|
83
|
+
zoneForPath(path) {
|
|
84
|
+
let matchedZone;
|
|
85
|
+
for (const zone of this.zones) {
|
|
86
|
+
if (!(path === zone.zonePrefix || path.startsWith(`${zone.zonePrefix}/`))) continue;
|
|
87
|
+
if (!matchedZone || zone.zonePrefix.length > matchedZone.zonePrefix.length) matchedZone = zone;
|
|
88
|
+
}
|
|
89
|
+
return matchedZone;
|
|
90
|
+
}
|
|
91
|
+
zoneForPrefix(zonePrefix) {
|
|
92
|
+
return this.zones.find((zone) => zone.zonePrefix === zonePrefix);
|
|
93
|
+
}
|
|
94
|
+
/** Check whether a path falls inside any configured zone. */
|
|
95
|
+
isInZone(path) {
|
|
96
|
+
return this.zoneForPath(path) !== void 0;
|
|
97
|
+
}
|
|
98
|
+
/** Build the full login URL for a zone, optionally with a post-auth redirect. */
|
|
99
|
+
loginUrl(zone, postAuthRedirectUri) {
|
|
100
|
+
const base = this._baseUrl + zone.loginPath;
|
|
101
|
+
if (postAuthRedirectUri) return `${base}?${new URLSearchParams({ [this._postAuthRedirectParam]: postAuthRedirectUri }).toString()}`;
|
|
102
|
+
return base;
|
|
103
|
+
}
|
|
104
|
+
loginUrlForZonePrefix(zonePrefix, postAuthRedirectUri) {
|
|
105
|
+
const zone = this.zoneForPrefix(zonePrefix);
|
|
106
|
+
return zone ? this.loginUrl(zone, postAuthRedirectUri) : null;
|
|
107
|
+
}
|
|
108
|
+
/** Build the full logout URL for a zone. */
|
|
109
|
+
logoutUrl(zone) {
|
|
110
|
+
return this._baseUrl + zone.logoutPath;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Handle a 401 response: if the current path is inside a zone,
|
|
114
|
+
* return a redirect instruction to the zone's login URL.
|
|
115
|
+
*/
|
|
116
|
+
handleUnauthorized(currentPath, responseStatus) {
|
|
117
|
+
if (responseStatus !== 401) return {
|
|
118
|
+
kind: AuthGuardResultKind.Ok,
|
|
119
|
+
value: null
|
|
120
|
+
};
|
|
121
|
+
const zone = this.zoneForPath(currentPath);
|
|
122
|
+
if (!zone) return {
|
|
123
|
+
kind: AuthGuardResultKind.Ok,
|
|
124
|
+
value: null
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
kind: AuthGuardResultKind.Redirect,
|
|
128
|
+
status: AuthGuardRedirectStatus.Found,
|
|
129
|
+
location: this.loginUrl(zone, currentPath)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
export { readBasicAuthBoundaryKind as n, BasicAuthContextClientConfigSchema as r, BasicAuthContextClient as t };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=client-D_0eAARH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-D_0eAARH.mjs","names":["BasicAuthBoundaryKindValues"],"sources":["../src/schemas.ts","../src/client.ts"],"sourcesContent":["// basic-auth-context-client — config validation schema\n//\n// Uses @standard-schema via the foundation createSchema helper to validate\n// BasicAuthContextClientConfig at the cross-boundary entry point.\n\nimport { createSchema } from \"@securitydept/client\";\nimport type { BasicAuthContextClientConfig } from \"./types\";\n\nfunction isNonEmptyString(v: unknown): v is string {\n\treturn typeof v === \"string\" && v.length > 0;\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n\treturn typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction validateZoneConfig(input: unknown, index: number) {\n\tconst issues: Array<{ message: string }> = [];\n\tif (!isObject(input)) {\n\t\tissues.push({ message: `zones[${index}]: expected an object` });\n\t\treturn { issues };\n\t}\n\tif (!isNonEmptyString(input.zonePrefix)) {\n\t\tissues.push({\n\t\t\tmessage: `zones[${index}].zonePrefix: expected a non-empty string`,\n\t\t});\n\t}\n\tif (\n\t\tinput.loginSubpath !== undefined &&\n\t\ttypeof input.loginSubpath !== \"string\"\n\t) {\n\t\tissues.push({\n\t\t\tmessage: `zones[${index}].loginSubpath: expected a string if provided`,\n\t\t});\n\t}\n\tif (\n\t\tinput.logoutSubpath !== undefined &&\n\t\ttypeof input.logoutSubpath !== \"string\"\n\t) {\n\t\tissues.push({\n\t\t\tmessage: `zones[${index}].logoutSubpath: expected a string if provided`,\n\t\t});\n\t}\n\treturn issues.length > 0 ? { issues } : null;\n}\n\n/**\n * Schema for validating BasicAuthContextClientConfig.\n *\n * Validates:\n * - `baseUrl` is a non-empty string\n * - `zones` is a non-empty array of valid zone configs\n * - each zone has a non-empty `zonePrefix`\n * - optional fields have correct types when provided\n */\nexport const BasicAuthContextClientConfigSchema =\n\tcreateSchema<BasicAuthContextClientConfig>({\n\t\tvalidate(input) {\n\t\t\tconst issues: Array<{ message: string }> = [];\n\n\t\t\tif (!isObject(input)) {\n\t\t\t\treturn { issues: [{ message: \"Expected an object\" }] };\n\t\t\t}\n\n\t\t\tif (!isNonEmptyString(input.baseUrl)) {\n\t\t\t\tissues.push({ message: \"baseUrl: expected a non-empty string\" });\n\t\t\t}\n\n\t\t\tif (!Array.isArray(input.zones) || input.zones.length === 0) {\n\t\t\t\tissues.push({\n\t\t\t\t\tmessage: \"zones: expected a non-empty array of zone configs\",\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tfor (let i = 0; i < input.zones.length; i++) {\n\t\t\t\t\tconst zoneResult = validateZoneConfig(input.zones[i], i);\n\t\t\t\t\tif (zoneResult) {\n\t\t\t\t\t\tissues.push(...zoneResult.issues);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tinput.postAuthRedirectParam !== undefined &&\n\t\t\t\ttypeof input.postAuthRedirectParam !== \"string\"\n\t\t\t) {\n\t\t\t\tissues.push({\n\t\t\t\t\tmessage: \"postAuthRedirectParam: expected a string if provided\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (issues.length > 0) {\n\t\t\t\treturn { issues };\n\t\t\t}\n\n\t\t\treturn { value: input as unknown as BasicAuthContextClientConfig };\n\t\t},\n\t});\n","import { validateWithSchemaSync } from \"@securitydept/client\";\nimport { BasicAuthContextClientConfigSchema } from \"./schemas\";\nimport type {\n\tAuthGuardResult,\n\tBasicAuthBoundaryKind,\n\tBasicAuthBoundaryObservation,\n\tBasicAuthContextClientConfig,\n\tBasicAuthZoneConfig,\n\tResolvedBasicAuthZone,\n} from \"./types\";\nimport {\n\tAuthGuardRedirectStatus,\n\tAuthGuardResultKind,\n\tBasicAuthBoundaryKind as BasicAuthBoundaryKindValues,\n} from \"./types\";\n\nconst DEFAULT_LOGIN_SUBPATH = \"/login\";\nconst DEFAULT_LOGOUT_SUBPATH = \"/logout\";\nconst DEFAULT_POST_AUTH_REDIRECT_PARAM = \"post_auth_redirect_uri\";\n\nfunction resolveZone(config: BasicAuthZoneConfig): ResolvedBasicAuthZone {\n\tconst prefix = config.zonePrefix.replace(/\\/+$/, \"\");\n\tconst loginSub = config.loginSubpath ?? DEFAULT_LOGIN_SUBPATH;\n\tconst logoutSub = config.logoutSubpath ?? DEFAULT_LOGOUT_SUBPATH;\n\n\treturn {\n\t\tzonePrefix: prefix,\n\t\tloginPath: prefix + loginSub,\n\t\tlogoutPath: prefix + logoutSub,\n\t};\n}\n\nexport function readBasicAuthBoundaryKind(\n\toptions: BasicAuthBoundaryObservation,\n): BasicAuthBoundaryKind {\n\tif (options.status < 400) {\n\t\treturn BasicAuthBoundaryKindValues.Authenticated;\n\t}\n\n\tif (options.challengeHeader) {\n\t\treturn BasicAuthBoundaryKindValues.Challenge;\n\t}\n\n\tif (options.status === 401 && options.requestPath === \"/basic/logout\") {\n\t\treturn BasicAuthBoundaryKindValues.LogoutPoison;\n\t}\n\n\treturn BasicAuthBoundaryKindValues.Unauthorized;\n}\n\n/**\n * Basic Auth Context Client.\n *\n * Thin client that manages zone boundary awareness and redirect instructions.\n * It does NOT manage credentials or HTTP auth headers — those are handled\n * by the browser's native Basic Auth UI triggered by 401 + WWW-Authenticate.\n */\nexport class BasicAuthContextClient {\n\treadonly zones: readonly ResolvedBasicAuthZone[];\n\tprivate readonly _baseUrl: string;\n\tprivate readonly _postAuthRedirectParam: string;\n\n\tconstructor(config: BasicAuthContextClientConfig) {\n\t\t// Deprecation-phase validation: warn on invalid config but do not throw.\n\t\t// A future minor release will escalate this to a hard error.\n\t\t// See 110-TS_SDK_MIGRATIONS.md for the full deprecation note.\n\t\tconst result = validateWithSchemaSync(\n\t\t\tBasicAuthContextClientConfigSchema,\n\t\t\tconfig,\n\t\t);\n\t\tif (!result.success) {\n\t\t\tconsole.warn(\n\t\t\t\t`[BasicAuthContextClient] Deprecated config shape detected (will become an error in a future release): ${result.issues.map((i) => i.message).join(\"; \")}`,\n\t\t\t);\n\t\t}\n\n\t\tthis._baseUrl = (config.baseUrl ?? \"\").replace(/\\/+$/, \"\");\n\t\tthis._postAuthRedirectParam =\n\t\t\tconfig.postAuthRedirectParam ?? DEFAULT_POST_AUTH_REDIRECT_PARAM;\n\t\tthis.zones = (config.zones ?? []).map(resolveZone);\n\t}\n\n\t/** Find the zone that contains the given path. */\n\tzoneForPath(path: string): ResolvedBasicAuthZone | undefined {\n\t\tlet matchedZone: ResolvedBasicAuthZone | undefined;\n\n\t\tfor (const zone of this.zones) {\n\t\t\tconst matches =\n\t\t\t\tpath === zone.zonePrefix || path.startsWith(`${zone.zonePrefix}/`);\n\t\t\tif (!matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\t!matchedZone ||\n\t\t\t\tzone.zonePrefix.length > matchedZone.zonePrefix.length\n\t\t\t) {\n\t\t\t\tmatchedZone = zone;\n\t\t\t}\n\t\t}\n\n\t\treturn matchedZone;\n\t}\n\n\tzoneForPrefix(zonePrefix: string): ResolvedBasicAuthZone | undefined {\n\t\treturn this.zones.find((zone) => zone.zonePrefix === zonePrefix);\n\t}\n\n\t/** Check whether a path falls inside any configured zone. */\n\tisInZone(path: string): boolean {\n\t\treturn this.zoneForPath(path) !== undefined;\n\t}\n\n\t/** Build the full login URL for a zone, optionally with a post-auth redirect. */\n\tloginUrl(zone: ResolvedBasicAuthZone, postAuthRedirectUri?: string): string {\n\t\tconst base = this._baseUrl + zone.loginPath;\n\t\tif (postAuthRedirectUri) {\n\t\t\tconst params = new URLSearchParams({\n\t\t\t\t[this._postAuthRedirectParam]: postAuthRedirectUri,\n\t\t\t});\n\t\t\treturn `${base}?${params.toString()}`;\n\t\t}\n\t\treturn base;\n\t}\n\n\tloginUrlForZonePrefix(\n\t\tzonePrefix: string,\n\t\tpostAuthRedirectUri?: string,\n\t): string | null {\n\t\tconst zone = this.zoneForPrefix(zonePrefix);\n\t\treturn zone ? this.loginUrl(zone, postAuthRedirectUri) : null;\n\t}\n\n\t/** Build the full logout URL for a zone. */\n\tlogoutUrl(zone: ResolvedBasicAuthZone): string {\n\t\treturn this._baseUrl + zone.logoutPath;\n\t}\n\n\t/**\n\t * Handle a 401 response: if the current path is inside a zone,\n\t * return a redirect instruction to the zone's login URL.\n\t */\n\thandleUnauthorized(\n\t\tcurrentPath: string,\n\t\tresponseStatus: number,\n\t): AuthGuardResult<null> {\n\t\tif (responseStatus !== 401) {\n\t\t\treturn { kind: AuthGuardResultKind.Ok, value: null };\n\t\t}\n\n\t\tconst zone = this.zoneForPath(currentPath);\n\t\tif (!zone) {\n\t\t\treturn { kind: AuthGuardResultKind.Ok, value: null };\n\t\t}\n\n\t\treturn {\n\t\t\tkind: AuthGuardResultKind.Redirect,\n\t\t\tstatus: AuthGuardRedirectStatus.Found,\n\t\t\tlocation: this.loginUrl(zone, currentPath),\n\t\t};\n\t}\n}\n"],"mappings":";;;AAQA,SAAS,iBAAiB,GAAyB;AAClD,QAAO,OAAO,MAAM,YAAY,EAAE,SAAS;;AAG5C,SAAS,SAAS,GAA0C;AAC3D,QAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE;;AAGhE,SAAS,mBAAmB,OAAgB,OAAe;CAC1D,MAAM,SAAqC,EAAE;AAC7C,KAAI,CAAC,SAAS,MAAM,EAAE;AACrB,SAAO,KAAK,EAAE,SAAS,SAAS,MAAM,wBAAwB,CAAC;AAC/D,SAAO,EAAE,QAAQ;;AAElB,KAAI,CAAC,iBAAiB,MAAM,WAAW,CACtC,QAAO,KAAK,EACX,SAAS,SAAS,MAAM,4CACxB,CAAC;AAEH,KACC,MAAM,iBAAiB,KAAA,KACvB,OAAO,MAAM,iBAAiB,SAE9B,QAAO,KAAK,EACX,SAAS,SAAS,MAAM,gDACxB,CAAC;AAEH,KACC,MAAM,kBAAkB,KAAA,KACxB,OAAO,MAAM,kBAAkB,SAE/B,QAAO,KAAK,EACX,SAAS,SAAS,MAAM,iDACxB,CAAC;AAEH,QAAO,OAAO,SAAS,IAAI,EAAE,QAAQ,GAAG;;;;;;;;;;;AAYzC,MAAa,qCACZ,aAA2C,EAC1C,SAAS,OAAO;CACf,MAAM,SAAqC,EAAE;AAE7C,KAAI,CAAC,SAAS,MAAM,CACnB,QAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,sBAAsB,CAAC,EAAE;AAGvD,KAAI,CAAC,iBAAiB,MAAM,QAAQ,CACnC,QAAO,KAAK,EAAE,SAAS,wCAAwC,CAAC;AAGjE,KAAI,CAAC,MAAM,QAAQ,MAAM,MAAM,IAAI,MAAM,MAAM,WAAW,EACzD,QAAO,KAAK,EACX,SAAS,qDACT,CAAC;KAEF,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,MAAM,QAAQ,KAAK;EAC5C,MAAM,aAAa,mBAAmB,MAAM,MAAM,IAAI,EAAE;AACxD,MAAI,WACH,QAAO,KAAK,GAAG,WAAW,OAAO;;AAKpC,KACC,MAAM,0BAA0B,KAAA,KAChC,OAAO,MAAM,0BAA0B,SAEvC,QAAO,KAAK,EACX,SAAS,wDACT,CAAC;AAGH,KAAI,OAAO,SAAS,EACnB,QAAO,EAAE,QAAQ;AAGlB,QAAO,EAAE,OAAO,OAAkD;GAEnE,CAAC;;;AChFH,MAAM,wBAAwB;AAC9B,MAAM,yBAAyB;AAC/B,MAAM,mCAAmC;AAEzC,SAAS,YAAY,QAAoD;CACxE,MAAM,SAAS,OAAO,WAAW,QAAQ,QAAQ,GAAG;CACpD,MAAM,WAAW,OAAO,gBAAgB;CACxC,MAAM,YAAY,OAAO,iBAAiB;AAE1C,QAAO;EACN,YAAY;EACZ,WAAW,SAAS;EACpB,YAAY,SAAS;EACrB;;AAGF,SAAgB,0BACf,SACwB;AACxB,KAAI,QAAQ,SAAS,IACpB,QAAOA,sBAA4B;AAGpC,KAAI,QAAQ,gBACX,QAAOA,sBAA4B;AAGpC,KAAI,QAAQ,WAAW,OAAO,QAAQ,gBAAgB,gBACrD,QAAOA,sBAA4B;AAGpC,QAAOA,sBAA4B;;;;;;;;;AAUpC,IAAa,yBAAb,MAAoC;CACnC;CACA;CACA;CAEA,YAAY,QAAsC;EAIjD,MAAM,SAAS,uBACd,oCACA,OACA;AACD,MAAI,CAAC,OAAO,QACX,SAAQ,KACP,yGAAyG,OAAO,OAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK,GACvJ;AAGF,OAAK,YAAY,OAAO,WAAW,IAAI,QAAQ,QAAQ,GAAG;AAC1D,OAAK,yBACJ,OAAO,yBAAyB;AACjC,OAAK,SAAS,OAAO,SAAS,EAAE,EAAE,IAAI,YAAY;;;CAInD,YAAY,MAAiD;EAC5D,IAAI;AAEJ,OAAK,MAAM,QAAQ,KAAK,OAAO;AAG9B,OAAI,EADH,SAAS,KAAK,cAAc,KAAK,WAAW,GAAG,KAAK,WAAW,GAAG,EAElE;AAGD,OACC,CAAC,eACD,KAAK,WAAW,SAAS,YAAY,WAAW,OAEhD,eAAc;;AAIhB,SAAO;;CAGR,cAAc,YAAuD;AACpE,SAAO,KAAK,MAAM,MAAM,SAAS,KAAK,eAAe,WAAW;;;CAIjE,SAAS,MAAuB;AAC/B,SAAO,KAAK,YAAY,KAAK,KAAK,KAAA;;;CAInC,SAAS,MAA6B,qBAAsC;EAC3E,MAAM,OAAO,KAAK,WAAW,KAAK;AAClC,MAAI,oBAIH,QAAO,GAAG,KAAK,GAHA,IAAI,gBAAgB,GACjC,KAAK,yBAAyB,qBAC/B,CAAC,CACuB,UAAU;AAEpC,SAAO;;CAGR,sBACC,YACA,qBACgB;EAChB,MAAM,OAAO,KAAK,cAAc,WAAW;AAC3C,SAAO,OAAO,KAAK,SAAS,MAAM,oBAAoB,GAAG;;;CAI1D,UAAU,MAAqC;AAC9C,SAAO,KAAK,WAAW,KAAK;;;;;;CAO7B,mBACC,aACA,gBACwB;AACxB,MAAI,mBAAmB,IACtB,QAAO;GAAE,MAAM,oBAAoB;GAAI,OAAO;GAAM;EAGrD,MAAM,OAAO,KAAK,YAAY,YAAY;AAC1C,MAAI,CAAC,KACJ,QAAO;GAAE,MAAM,oBAAoB;GAAI,OAAO;GAAM;AAGrD,SAAO;GACN,MAAM,oBAAoB;GAC1B,QAAQ,wBAAwB;GAChC,UAAU,KAAK,SAAS,MAAM,YAAY;GAC1C"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { a as AuthGuardResultKind, c as BasicAuthContextClientConfig, i as AuthGuardResult, l as BasicAuthZoneConfig, n as readBasicAuthBoundaryKind, o as BasicAuthBoundaryKind, r as AuthGuardRedirectStatus, s as BasicAuthBoundaryObservation, t as BasicAuthContextClient, u as ResolvedBasicAuthZone } from "./client-DU98lpa_.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../../../../node_modules/.pnpm/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts
|
|
4
|
+
/** The Standard Typed interface. This is a base type extended by other specs. */
|
|
5
|
+
interface StandardTypedV1<Input = unknown, Output = Input> {
|
|
6
|
+
/** The Standard properties. */
|
|
7
|
+
readonly "~standard": StandardTypedV1.Props<Input, Output>;
|
|
8
|
+
}
|
|
9
|
+
declare namespace StandardTypedV1 {
|
|
10
|
+
/** The Standard Typed properties interface. */
|
|
11
|
+
interface Props<Input = unknown, Output = Input> {
|
|
12
|
+
/** The version number of the standard. */
|
|
13
|
+
readonly version: 1;
|
|
14
|
+
/** The vendor name of the schema library. */
|
|
15
|
+
readonly vendor: string;
|
|
16
|
+
/** Inferred types associated with the schema. */
|
|
17
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** The Standard Typed types interface. */
|
|
20
|
+
interface Types<Input = unknown, Output = Input> {
|
|
21
|
+
/** The input type of the schema. */
|
|
22
|
+
readonly input: Input;
|
|
23
|
+
/** The output type of the schema. */
|
|
24
|
+
readonly output: Output;
|
|
25
|
+
}
|
|
26
|
+
/** Infers the input type of a Standard Typed. */
|
|
27
|
+
type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
|
28
|
+
/** Infers the output type of a Standard Typed. */
|
|
29
|
+
type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
|
30
|
+
}
|
|
31
|
+
/** The Standard Schema interface. */
|
|
32
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
33
|
+
/** The Standard Schema properties. */
|
|
34
|
+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
|
35
|
+
}
|
|
36
|
+
declare namespace StandardSchemaV1 {
|
|
37
|
+
/** The Standard Schema properties interface. */
|
|
38
|
+
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
|
|
39
|
+
/** Validates unknown input values. */
|
|
40
|
+
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
|
|
41
|
+
}
|
|
42
|
+
/** The result interface of the validate function. */
|
|
43
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
44
|
+
/** The result interface if validation succeeds. */
|
|
45
|
+
interface SuccessResult<Output> {
|
|
46
|
+
/** The typed output value. */
|
|
47
|
+
readonly value: Output;
|
|
48
|
+
/** A falsy value for `issues` indicates success. */
|
|
49
|
+
readonly issues?: undefined;
|
|
50
|
+
}
|
|
51
|
+
interface Options {
|
|
52
|
+
/** Explicit support for additional vendor-specific parameters, if needed. */
|
|
53
|
+
readonly libraryOptions?: Record<string, unknown> | undefined;
|
|
54
|
+
}
|
|
55
|
+
/** The result interface if validation fails. */
|
|
56
|
+
interface FailureResult {
|
|
57
|
+
/** The issues of failed validation. */
|
|
58
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
59
|
+
}
|
|
60
|
+
/** The issue interface of the failure output. */
|
|
61
|
+
interface Issue {
|
|
62
|
+
/** The error message of the issue. */
|
|
63
|
+
readonly message: string;
|
|
64
|
+
/** The path of the issue, if any. */
|
|
65
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
66
|
+
}
|
|
67
|
+
/** The path segment interface of the issue. */
|
|
68
|
+
interface PathSegment {
|
|
69
|
+
/** The key representing a path segment. */
|
|
70
|
+
readonly key: PropertyKey;
|
|
71
|
+
}
|
|
72
|
+
/** The Standard types interface. */
|
|
73
|
+
interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
|
|
74
|
+
/** Infers the input type of a Standard. */
|
|
75
|
+
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
|
|
76
|
+
/** Infers the output type of a Standard. */
|
|
77
|
+
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
|
|
78
|
+
}
|
|
79
|
+
/** The Standard JSON Schema interface. */
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/schemas.d.ts
|
|
82
|
+
/**
|
|
83
|
+
* Schema for validating BasicAuthContextClientConfig.
|
|
84
|
+
*
|
|
85
|
+
* Validates:
|
|
86
|
+
* - `baseUrl` is a non-empty string
|
|
87
|
+
* - `zones` is a non-empty array of valid zone configs
|
|
88
|
+
* - each zone has a non-empty `zonePrefix`
|
|
89
|
+
* - optional fields have correct types when provided
|
|
90
|
+
*/
|
|
91
|
+
declare const BasicAuthContextClientConfigSchema: StandardSchemaV1<unknown, BasicAuthContextClientConfig>;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { AuthGuardRedirectStatus, type AuthGuardResult, AuthGuardResultKind, BasicAuthBoundaryKind, type BasicAuthBoundaryObservation, BasicAuthContextClient, type BasicAuthContextClientConfig, BasicAuthContextClientConfigSchema, type BasicAuthZoneConfig, type ResolvedBasicAuthZone, readBasicAuthBoundaryKind };
|
|
94
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { n as readBasicAuthBoundaryKind, r as BasicAuthContextClientConfigSchema, t as BasicAuthContextClient } from "./client-D_0eAARH.mjs";
|
|
2
|
+
import { n as AuthGuardResultKind, r as BasicAuthBoundaryKind, t as AuthGuardRedirectStatus } from "./types-2IoKrgnl.mjs";
|
|
3
|
+
export { AuthGuardRedirectStatus, AuthGuardResultKind, BasicAuthBoundaryKind, BasicAuthContextClient, BasicAuthContextClientConfigSchema, readBasicAuthBoundaryKind };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { c as BasicAuthContextClientConfig, t as BasicAuthContextClient } from "../client-DU98lpa_.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/server/helpers.d.ts
|
|
4
|
+
/** Minimal server request context — host-neutral. */
|
|
5
|
+
interface ServerRequestContext {
|
|
6
|
+
/** The request path (e.g. "/api/protected"). */
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Server-side redirect instruction for the host to act on.
|
|
11
|
+
*
|
|
12
|
+
* The host translates this into its framework-specific response:
|
|
13
|
+
* - Next.js: `{ redirect: { destination, statusCode } }`
|
|
14
|
+
* - Express: `res.redirect(statusCode, destination)`
|
|
15
|
+
* - etc.
|
|
16
|
+
*/
|
|
17
|
+
interface ServerRedirectInstruction {
|
|
18
|
+
/** HTTP redirect status code. */
|
|
19
|
+
statusCode: number;
|
|
20
|
+
/** Redirect target URL. */
|
|
21
|
+
destination: string;
|
|
22
|
+
}
|
|
23
|
+
/** Options for {@link createBasicAuthServerHelper}. */
|
|
24
|
+
interface CreateBasicAuthServerHelperOptions {
|
|
25
|
+
/** BasicAuthContextClient config — same shape as the browser client. */
|
|
26
|
+
config: BasicAuthContextClientConfig;
|
|
27
|
+
}
|
|
28
|
+
/** Server-host helper for basic-auth zone-based redirect logic. */
|
|
29
|
+
interface BasicAuthServerHelper {
|
|
30
|
+
/** The underlying BasicAuthContextClient instance. */
|
|
31
|
+
readonly client: BasicAuthContextClient;
|
|
32
|
+
/**
|
|
33
|
+
* Handle a 401 response from the backend in a server context.
|
|
34
|
+
*
|
|
35
|
+
* If the request path falls inside a configured zone, returns a
|
|
36
|
+
* redirect instruction. Otherwise returns `null`.
|
|
37
|
+
*/
|
|
38
|
+
handleUnauthorized(context: ServerRequestContext): ServerRedirectInstruction | null;
|
|
39
|
+
/**
|
|
40
|
+
* Build the login URL for a specific path.
|
|
41
|
+
*
|
|
42
|
+
* Returns the login URL for the zone containing the path,
|
|
43
|
+
* or `null` if the path isn't in any zone.
|
|
44
|
+
*/
|
|
45
|
+
loginUrlForPath(path: string): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Build the logout URL for a specific path.
|
|
48
|
+
*
|
|
49
|
+
* Returns the logout URL for the zone containing the path,
|
|
50
|
+
* or `null` if the path isn't in any zone.
|
|
51
|
+
*/
|
|
52
|
+
logoutUrlForPath(path: string): string | null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a server-host helper for basic-auth zone management.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const helper = createBasicAuthServerHelper({
|
|
60
|
+
* config: { baseUrl: "https://auth.example.com", zones: [{ zonePrefix: "/api" }] },
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* // In a server request handler:
|
|
64
|
+
* const redirect = helper.handleUnauthorized({ path: "/api/data" });
|
|
65
|
+
* if (redirect) {
|
|
66
|
+
* return Response.redirect(redirect.destination, redirect.statusCode);
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function createBasicAuthServerHelper(options: CreateBasicAuthServerHelperOptions): BasicAuthServerHelper;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { type BasicAuthServerHelper, type CreateBasicAuthServerHelperOptions, type ServerRedirectInstruction, type ServerRequestContext, createBasicAuthServerHelper };
|
|
73
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { t as BasicAuthContextClient } from "../client-D_0eAARH.mjs";
|
|
2
|
+
import { n as AuthGuardResultKind } from "../types-2IoKrgnl.mjs";
|
|
3
|
+
//#region src/server/helpers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a server-host helper for basic-auth zone management.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const helper = createBasicAuthServerHelper({
|
|
10
|
+
* config: { baseUrl: "https://auth.example.com", zones: [{ zonePrefix: "/api" }] },
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* // In a server request handler:
|
|
14
|
+
* const redirect = helper.handleUnauthorized({ path: "/api/data" });
|
|
15
|
+
* if (redirect) {
|
|
16
|
+
* return Response.redirect(redirect.destination, redirect.statusCode);
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function createBasicAuthServerHelper(options) {
|
|
21
|
+
const client = new BasicAuthContextClient(options.config);
|
|
22
|
+
return {
|
|
23
|
+
client,
|
|
24
|
+
handleUnauthorized(context) {
|
|
25
|
+
const result = client.handleUnauthorized(context.path, 401);
|
|
26
|
+
if (result.kind === AuthGuardResultKind.Redirect) return {
|
|
27
|
+
statusCode: result.status,
|
|
28
|
+
destination: result.location
|
|
29
|
+
};
|
|
30
|
+
return null;
|
|
31
|
+
},
|
|
32
|
+
loginUrlForPath(path) {
|
|
33
|
+
const zone = client.zoneForPath(path);
|
|
34
|
+
if (!zone) return null;
|
|
35
|
+
return client.loginUrl(zone, path);
|
|
36
|
+
},
|
|
37
|
+
logoutUrlForPath(path) {
|
|
38
|
+
const zone = client.zoneForPath(path);
|
|
39
|
+
if (!zone) return null;
|
|
40
|
+
return client.logoutUrl(zone);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { createBasicAuthServerHelper };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/server/helpers.ts"],"sourcesContent":["// Server-host helpers for basic-auth-context-client\n//\n// Provides host-neutral server helpers that wrap BasicAuthContextClient\n// for SSR / server-render-host scenarios (Next.js, Remix, Astro,\n// plain Node request handlers, etc.).\n//\n// The host owns HTTP response construction (302 headers, response body).\n// These helpers produce redirect instructions the host can act on.\n\nimport { BasicAuthContextClient } from \"../client\";\nimport type { AuthGuardResult, BasicAuthContextClientConfig } from \"../types\";\nimport { AuthGuardResultKind } from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Server request context\n// ---------------------------------------------------------------------------\n\n/** Minimal server request context — host-neutral. */\nexport interface ServerRequestContext {\n\t/** The request path (e.g. \"/api/protected\"). */\n\tpath: string;\n}\n\n// ---------------------------------------------------------------------------\n// Redirect instruction\n// ---------------------------------------------------------------------------\n\n/**\n * Server-side redirect instruction for the host to act on.\n *\n * The host translates this into its framework-specific response:\n * - Next.js: `{ redirect: { destination, statusCode } }`\n * - Express: `res.redirect(statusCode, destination)`\n * - etc.\n */\nexport interface ServerRedirectInstruction {\n\t/** HTTP redirect status code. */\n\tstatusCode: number;\n\t/** Redirect target URL. */\n\tdestination: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helper options\n// ---------------------------------------------------------------------------\n\n/** Options for {@link createBasicAuthServerHelper}. */\nexport interface CreateBasicAuthServerHelperOptions {\n\t/** BasicAuthContextClient config — same shape as the browser client. */\n\tconfig: BasicAuthContextClientConfig;\n}\n\n// ---------------------------------------------------------------------------\n// Helper interface\n// ---------------------------------------------------------------------------\n\n/** Server-host helper for basic-auth zone-based redirect logic. */\nexport interface BasicAuthServerHelper {\n\t/** The underlying BasicAuthContextClient instance. */\n\treadonly client: BasicAuthContextClient;\n\n\t/**\n\t * Handle a 401 response from the backend in a server context.\n\t *\n\t * If the request path falls inside a configured zone, returns a\n\t * redirect instruction. Otherwise returns `null`.\n\t */\n\thandleUnauthorized(\n\t\tcontext: ServerRequestContext,\n\t): ServerRedirectInstruction | null;\n\n\t/**\n\t * Build the login URL for a specific path.\n\t *\n\t * Returns the login URL for the zone containing the path,\n\t * or `null` if the path isn't in any zone.\n\t */\n\tloginUrlForPath(path: string): string | null;\n\n\t/**\n\t * Build the logout URL for a specific path.\n\t *\n\t * Returns the logout URL for the zone containing the path,\n\t * or `null` if the path isn't in any zone.\n\t */\n\tlogoutUrlForPath(path: string): string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a server-host helper for basic-auth zone management.\n *\n * @example\n * ```ts\n * const helper = createBasicAuthServerHelper({\n * config: { baseUrl: \"https://auth.example.com\", zones: [{ zonePrefix: \"/api\" }] },\n * });\n *\n * // In a server request handler:\n * const redirect = helper.handleUnauthorized({ path: \"/api/data\" });\n * if (redirect) {\n * return Response.redirect(redirect.destination, redirect.statusCode);\n * }\n * ```\n */\nexport function createBasicAuthServerHelper(\n\toptions: CreateBasicAuthServerHelperOptions,\n): BasicAuthServerHelper {\n\tconst client = new BasicAuthContextClient(options.config);\n\n\treturn {\n\t\tclient,\n\n\t\thandleUnauthorized(\n\t\t\tcontext: ServerRequestContext,\n\t\t): ServerRedirectInstruction | null {\n\t\t\tconst result: AuthGuardResult<null> = client.handleUnauthorized(\n\t\t\t\tcontext.path,\n\t\t\t\t401,\n\t\t\t);\n\n\t\t\tif (result.kind === AuthGuardResultKind.Redirect) {\n\t\t\t\treturn {\n\t\t\t\t\tstatusCode: result.status,\n\t\t\t\t\tdestination: result.location,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\n\t\tloginUrlForPath(path: string): string | null {\n\t\t\tconst zone = client.zoneForPath(path);\n\t\t\tif (!zone) return null;\n\t\t\treturn client.loginUrl(zone, path);\n\t\t},\n\n\t\tlogoutUrlForPath(path: string): string | null {\n\t\t\tconst zone = client.zoneForPath(path);\n\t\t\tif (!zone) return null;\n\t\t\treturn client.logoutUrl(zone);\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4GA,SAAgB,4BACf,SACwB;CACxB,MAAM,SAAS,IAAI,uBAAuB,QAAQ,OAAO;AAEzD,QAAO;EACN;EAEA,mBACC,SACmC;GACnC,MAAM,SAAgC,OAAO,mBAC5C,QAAQ,MACR,IACA;AAED,OAAI,OAAO,SAAS,oBAAoB,SACvC,QAAO;IACN,YAAY,OAAO;IACnB,aAAa,OAAO;IACpB;AAGF,UAAO;;EAGR,gBAAgB,MAA6B;GAC5C,MAAM,OAAO,OAAO,YAAY,KAAK;AACrC,OAAI,CAAC,KAAM,QAAO;AAClB,UAAO,OAAO,SAAS,MAAM,KAAK;;EAGnC,iBAAiB,MAA6B;GAC7C,MAAM,OAAO,OAAO,YAAY,KAAK;AACrC,OAAI,CAAC,KAAM,QAAO;AAClB,UAAO,OAAO,UAAU,KAAK;;EAE9B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
const BasicAuthBoundaryKind = {
|
|
3
|
+
Authenticated: "authenticated",
|
|
4
|
+
Challenge: "challenge",
|
|
5
|
+
Unauthorized: "unauthorized",
|
|
6
|
+
LogoutPoison: "logout_poison"
|
|
7
|
+
};
|
|
8
|
+
const AuthGuardResultKind = {
|
|
9
|
+
Ok: "ok",
|
|
10
|
+
Redirect: "redirect"
|
|
11
|
+
};
|
|
12
|
+
const AuthGuardRedirectStatus = {
|
|
13
|
+
Found: 302,
|
|
14
|
+
SeeOther: 303,
|
|
15
|
+
TemporaryRedirect: 307
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { AuthGuardResultKind as n, BasicAuthBoundaryKind as r, AuthGuardRedirectStatus as t };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=types-2IoKrgnl.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-2IoKrgnl.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["// --- Basic Auth Context Client types ---\n\n/** Configuration for a single Basic Auth zone. */\nexport interface BasicAuthZoneConfig {\n\t/** URL path prefix for this zone (e.g. \"/basic\"). */\n\tzonePrefix: string;\n\t/** Login subpath relative to zone prefix (default: \"/login\"). */\n\tloginSubpath?: string;\n\t/** Logout subpath relative to zone prefix (default: \"/logout\"). */\n\tlogoutSubpath?: string;\n}\n\n/** Configuration for the Basic Auth Context Client. */\nexport interface BasicAuthContextClientConfig {\n\t/** Base URL of the SecurityDept server. */\n\tbaseUrl: string;\n\t/** One or more Basic Auth zones to manage. */\n\tzones: BasicAuthZoneConfig[];\n\t/** Name of the query parameter for post-auth redirect (default: \"post_auth_redirect_uri\"). */\n\tpostAuthRedirectParam?: string;\n}\n\nexport const BasicAuthBoundaryKind = {\n\tAuthenticated: \"authenticated\",\n\tChallenge: \"challenge\",\n\tUnauthorized: \"unauthorized\",\n\tLogoutPoison: \"logout_poison\",\n} as const;\n\nexport type BasicAuthBoundaryKind =\n\t(typeof BasicAuthBoundaryKind)[keyof typeof BasicAuthBoundaryKind];\n\nexport interface BasicAuthBoundaryObservation {\n\tstatus: number;\n\tchallengeHeader?: string | null;\n\trequestPath?: string;\n}\n\nexport const AuthGuardResultKind = {\n\tOk: \"ok\",\n\tRedirect: \"redirect\",\n} as const;\n\nexport type AuthGuardResultKind =\n\t(typeof AuthGuardResultKind)[keyof typeof AuthGuardResultKind];\n\nexport const AuthGuardRedirectStatus = {\n\tFound: 302,\n\tSeeOther: 303,\n\tTemporaryRedirect: 307,\n} as const;\n\nexport type AuthGuardRedirectStatus =\n\t(typeof AuthGuardRedirectStatus)[keyof typeof AuthGuardRedirectStatus];\n\n/** Redirect instruction — framework-neutral result. */\nexport type AuthGuardResult<T> =\n\t| { kind: typeof AuthGuardResultKind.Ok; value: T }\n\t| {\n\t\t\tkind: typeof AuthGuardResultKind.Redirect;\n\t\t\tstatus: AuthGuardRedirectStatus;\n\t\t\tlocation: string;\n\t };\n\n/** Resolved zone with computed paths. */\nexport interface ResolvedBasicAuthZone {\n\tzonePrefix: string;\n\tloginPath: string;\n\tlogoutPath: string;\n}\n"],"mappings":";AAsBA,MAAa,wBAAwB;CACpC,eAAe;CACf,WAAW;CACX,cAAc;CACd,cAAc;CACd;AAWD,MAAa,sBAAsB;CAClC,IAAI;CACJ,UAAU;CACV;AAKD,MAAa,0BAA0B;CACtC,OAAO;CACP,UAAU;CACV,mBAAmB;CACnB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { i as AuthGuardResult, t as BasicAuthContextClient } from "../client-DU98lpa_.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/web/login-redirect.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link loginWithRedirect}.
|
|
6
|
+
*/
|
|
7
|
+
interface LoginWithRedirectOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The current request path to resolve the target zone.
|
|
10
|
+
*
|
|
11
|
+
* When omitted, `window.location.pathname` is used.
|
|
12
|
+
*/
|
|
13
|
+
currentPath?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Where to redirect the user after successful authentication.
|
|
16
|
+
*
|
|
17
|
+
* When omitted, `window.location.href` is used as the return URI.
|
|
18
|
+
*/
|
|
19
|
+
postAuthRedirectUri?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One-shot browser redirect to the zone login endpoint for the given path.
|
|
23
|
+
*
|
|
24
|
+
* 1. Resolves the zone for the current (or specified) path.
|
|
25
|
+
* 2. Navigates the current window to the zone's login URL with a
|
|
26
|
+
* post-auth redirect parameter.
|
|
27
|
+
*
|
|
28
|
+
* Returns `false` if no zone matches the path (no redirect performed).
|
|
29
|
+
* Returns `true` if the redirect was initiated.
|
|
30
|
+
*
|
|
31
|
+
* This is the recommended browser entry point for initiating basic-auth
|
|
32
|
+
* login. It is intentionally a standalone function (not a method on
|
|
33
|
+
* `BasicAuthContextClient`) to keep browser-specific side-effects out
|
|
34
|
+
* of the host-neutral client contract.
|
|
35
|
+
*/
|
|
36
|
+
declare function loginWithRedirect(client: BasicAuthContextClient, options?: LoginWithRedirectOptions): boolean;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/web/redirect.d.ts
|
|
39
|
+
/**
|
|
40
|
+
* Perform a browser redirect for an `AuthGuardResult` that requires redirection.
|
|
41
|
+
*/
|
|
42
|
+
declare function performRedirect(result: AuthGuardResult<unknown>): void;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { type LoginWithRedirectOptions, loginWithRedirect, performRedirect };
|
|
45
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { n as AuthGuardResultKind } from "../types-2IoKrgnl.mjs";
|
|
2
|
+
//#region src/web/login-redirect.ts
|
|
3
|
+
/**
|
|
4
|
+
* One-shot browser redirect to the zone login endpoint for the given path.
|
|
5
|
+
*
|
|
6
|
+
* 1. Resolves the zone for the current (or specified) path.
|
|
7
|
+
* 2. Navigates the current window to the zone's login URL with a
|
|
8
|
+
* post-auth redirect parameter.
|
|
9
|
+
*
|
|
10
|
+
* Returns `false` if no zone matches the path (no redirect performed).
|
|
11
|
+
* Returns `true` if the redirect was initiated.
|
|
12
|
+
*
|
|
13
|
+
* This is the recommended browser entry point for initiating basic-auth
|
|
14
|
+
* login. It is intentionally a standalone function (not a method on
|
|
15
|
+
* `BasicAuthContextClient`) to keep browser-specific side-effects out
|
|
16
|
+
* of the host-neutral client contract.
|
|
17
|
+
*/
|
|
18
|
+
function loginWithRedirect(client, options = {}) {
|
|
19
|
+
const currentPath = options.currentPath ?? window.location.pathname;
|
|
20
|
+
const zone = client.zoneForPath(currentPath);
|
|
21
|
+
if (!zone) return false;
|
|
22
|
+
const postAuthRedirectUri = options.postAuthRedirectUri ?? window.location.href;
|
|
23
|
+
window.location.href = client.loginUrl(zone, postAuthRedirectUri);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/web/redirect.ts
|
|
28
|
+
/**
|
|
29
|
+
* Perform a browser redirect for an `AuthGuardResult` that requires redirection.
|
|
30
|
+
*/
|
|
31
|
+
function performRedirect(result) {
|
|
32
|
+
if (result.kind === AuthGuardResultKind.Redirect) globalThis.location.href = result.location;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { loginWithRedirect, performRedirect };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/web/login-redirect.ts","../../src/web/redirect.ts"],"sourcesContent":["// Basic Auth Context Client — Browser login redirect convenience\n//\n// Provides a one-shot browser redirect to a zone's login page, matching the\n// convenience pattern established by session-context-client/web.\n//\n// Unlike session-context where there is a single login URL, basic-auth is\n// zone-based: the adopter must identify which zone to target. This helper\n// resolves the zone from a path and performs the redirect.\n//\n// Stability: provisional\n\nimport type { BasicAuthContextClient } from \"../client\";\n\n/**\n * Options for {@link loginWithRedirect}.\n */\nexport interface LoginWithRedirectOptions {\n\t/**\n\t * The current request path to resolve the target zone.\n\t *\n\t * When omitted, `window.location.pathname` is used.\n\t */\n\tcurrentPath?: string;\n\n\t/**\n\t * Where to redirect the user after successful authentication.\n\t *\n\t * When omitted, `window.location.href` is used as the return URI.\n\t */\n\tpostAuthRedirectUri?: string;\n}\n\n/**\n * One-shot browser redirect to the zone login endpoint for the given path.\n *\n * 1. Resolves the zone for the current (or specified) path.\n * 2. Navigates the current window to the zone's login URL with a\n * post-auth redirect parameter.\n *\n * Returns `false` if no zone matches the path (no redirect performed).\n * Returns `true` if the redirect was initiated.\n *\n * This is the recommended browser entry point for initiating basic-auth\n * login. It is intentionally a standalone function (not a method on\n * `BasicAuthContextClient`) to keep browser-specific side-effects out\n * of the host-neutral client contract.\n */\nexport function loginWithRedirect(\n\tclient: BasicAuthContextClient,\n\toptions: LoginWithRedirectOptions = {},\n): boolean {\n\tconst currentPath = options.currentPath ?? window.location.pathname;\n\tconst zone = client.zoneForPath(currentPath);\n\tif (!zone) {\n\t\treturn false;\n\t}\n\n\tconst postAuthRedirectUri =\n\t\toptions.postAuthRedirectUri ?? window.location.href;\n\twindow.location.href = client.loginUrl(zone, postAuthRedirectUri);\n\treturn true;\n}\n","import type { AuthGuardResult } from \"../types\";\nimport { AuthGuardResultKind } from \"../types\";\n\n/**\n * Perform a browser redirect for an `AuthGuardResult` that requires redirection.\n */\nexport function performRedirect(result: AuthGuardResult<unknown>): void {\n\tif (result.kind === AuthGuardResultKind.Redirect) {\n\t\t// Accessing location via window to satisfy strict type checks.\n\t\t(globalThis as unknown as { location: { href: string } }).location.href =\n\t\t\tresult.location;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA+CA,SAAgB,kBACf,QACA,UAAoC,EAAE,EAC5B;CACV,MAAM,cAAc,QAAQ,eAAe,OAAO,SAAS;CAC3D,MAAM,OAAO,OAAO,YAAY,YAAY;AAC5C,KAAI,CAAC,KACJ,QAAO;CAGR,MAAM,sBACL,QAAQ,uBAAuB,OAAO,SAAS;AAChD,QAAO,SAAS,OAAO,OAAO,SAAS,MAAM,oBAAoB;AACjE,QAAO;;;;;;;ACtDR,SAAgB,gBAAgB,QAAwC;AACvE,KAAI,OAAO,SAAS,oBAAoB,SAEtC,YAAyD,SAAS,OAClE,OAAO"}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@securitydept/basic-auth-context-client",
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"monorepo-tsc": "./src/index.ts",
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
|
+
"import": "./dist/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./web": {
|
|
13
|
+
"monorepo-tsc": "./src/web/index.ts",
|
|
14
|
+
"types": "./dist/web/index.d.mts",
|
|
15
|
+
"import": "./dist/web/index.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./server": {
|
|
18
|
+
"monorepo-tsc": "./src/server/index.ts",
|
|
19
|
+
"types": "./dist/server/index.d.mts",
|
|
20
|
+
"import": "./dist/server/index.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"typecheck": "tsc -b"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@securitydept/client": "workspace:*"
|
|
32
|
+
}
|
|
33
|
+
}
|