@flink-app/oidc-plugin 2.0.0-alpha.116 → 2.0.0-alpha.118
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/CHANGELOG.md +55 -0
- package/README.md +296 -17
- package/dist/OidcPlugin.d.ts.map +1 -1
- package/dist/OidcPlugin.js +35 -2
- package/dist/OidcPluginContext.d.ts +17 -0
- package/dist/OidcPluginContext.d.ts.map +1 -1
- package/dist/OidcPluginOptions.d.ts +123 -3
- package/dist/OidcPluginOptions.d.ts.map +1 -1
- package/dist/handlers/CallbackOidc.d.ts.map +1 -1
- package/dist/handlers/CallbackOidc.js +97 -20
- package/dist/handlers/InitiateOidc.d.ts.map +1 -1
- package/dist/handlers/InitiateOidc.js +12 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/providers/ProviderRegistry.d.ts +27 -4
- package/dist/providers/ProviderRegistry.d.ts.map +1 -1
- package/dist/providers/ProviderRegistry.js +77 -21
- package/dist/utils/error-utils.d.ts +1 -0
- package/dist/utils/error-utils.d.ts.map +1 -1
- package/dist/utils/error-utils.js +1 -0
- package/dist/utils/redirect-uri-utils.d.ts +38 -0
- package/dist/utils/redirect-uri-utils.d.ts.map +1 -0
- package/dist/utils/redirect-uri-utils.js +92 -0
- package/examples/basic-oidc.ts +4 -0
- package/examples/multi-provider.ts +4 -0
- package/package.json +6 -6
- package/spec/handlers/CallbackOidc.spec.ts +330 -0
- package/spec/handlers/InitiateOidc.spec.ts +95 -1
- package/spec/helpers/test-helpers.ts +68 -0
- package/spec/plugin/OidcPlugin.spec.ts +165 -0
- package/spec/providers/ProviderRegistry.spec.ts +142 -0
- package/spec/utils/redirect-uri-utils.spec.ts +98 -0
- package/src/OidcPlugin.ts +38 -1
- package/src/OidcPluginContext.ts +18 -0
- package/src/OidcPluginOptions.ts +132 -3
- package/src/handlers/CallbackOidc.ts +109 -20
- package/src/handlers/InitiateOidc.ts +14 -3
- package/src/index.ts +10 -1
- package/src/providers/ProviderRegistry.ts +95 -22
- package/src/utils/error-utils.ts +1 -0
- package/src/utils/redirect-uri-utils.ts +116 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirect URI allow-list utilities
|
|
3
|
+
*
|
|
4
|
+
* The `?redirectUri=` query parameter on `/oidc/:provider/initiate` decides where the
|
|
5
|
+
* host's JWT is delivered once the OIDC flow completes. It is attacker-controllable —
|
|
6
|
+
* anyone can craft an initiate link — so deployments should restrict it to the URLs
|
|
7
|
+
* their own clients use via the `allowedRedirectUris` plugin option.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Allow-list for the `redirectUri` query parameter.
|
|
12
|
+
*
|
|
13
|
+
* Either a list of entries or a predicate:
|
|
14
|
+
* - An entry that is a bare origin (`https://app.example.com`, `myapp://cb`) matches any
|
|
15
|
+
* path/query/fragment below that origin.
|
|
16
|
+
* - An entry with a path (`https://app.example.com/login`) matches that exact URI only.
|
|
17
|
+
* - A predicate receives the requested redirectUri and the provider name.
|
|
18
|
+
*/
|
|
19
|
+
export type AllowedRedirectUris = string[] | ((redirectUri: string, provider: string) => boolean);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether a redirect target is a relative path on the plugin's own origin.
|
|
23
|
+
*
|
|
24
|
+
* `/dashboard` and `/login?error=x` are relative. `//evil.example` (protocol-relative)
|
|
25
|
+
* and `/\evil.example` are NOT — browsers resolve both of those to another origin.
|
|
26
|
+
*/
|
|
27
|
+
export function isRelativeRedirect(redirectUri: string): boolean {
|
|
28
|
+
return /^\/(?![/\\])/.test(redirectUri);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseUrl(value: string): URL | undefined {
|
|
32
|
+
try {
|
|
33
|
+
return new URL(value);
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Origin of a URL, including custom schemes.
|
|
41
|
+
* `URL.origin` is the string "null" for non-special schemes (e.g. `myapp://cb`),
|
|
42
|
+
* so fall back to scheme + host for those.
|
|
43
|
+
*/
|
|
44
|
+
function originOf(url: URL): string {
|
|
45
|
+
return url.origin && url.origin !== "null" ? url.origin : `${url.protocol}//${url.host}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether an allow-list entry names a bare origin (no path, query or fragment)
|
|
50
|
+
* and therefore matches every URI below that origin.
|
|
51
|
+
*/
|
|
52
|
+
function isOriginOnlyEntry(url: URL): boolean {
|
|
53
|
+
return (url.pathname === "" || url.pathname === "/") && !url.search && !url.hash;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Match a redirect URI against a single allow-list entry.
|
|
58
|
+
* Exact string match always wins; origin-only entries additionally match by origin.
|
|
59
|
+
*/
|
|
60
|
+
function matchesEntry(redirectUri: string, entry: string): boolean {
|
|
61
|
+
if (redirectUri === entry) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const entryUrl = parseUrl(entry);
|
|
66
|
+
if (!entryUrl || !isOriginOnlyEntry(entryUrl)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const targetUrl = parseUrl(redirectUri);
|
|
71
|
+
if (!targetUrl) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return originOf(targetUrl) === originOf(entryUrl);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check whether a redirect URI is allowed.
|
|
80
|
+
*
|
|
81
|
+
* @param redirectUri - The redirect target to check
|
|
82
|
+
* @param provider - Provider name (passed to the predicate form)
|
|
83
|
+
* @param allowedRedirectUris - The `allowedRedirectUris` plugin option. Undefined means
|
|
84
|
+
* no allow-list is configured and every URI is accepted (legacy behaviour).
|
|
85
|
+
* @param alwaysAllowed - Entries that bypass the allow-list, e.g. the provider's own
|
|
86
|
+
* `callbackUrl` and the redirectUri already validated at initiate time.
|
|
87
|
+
* @returns true if the redirect may be performed
|
|
88
|
+
*/
|
|
89
|
+
export function isRedirectUriAllowed(
|
|
90
|
+
redirectUri: string,
|
|
91
|
+
provider: string,
|
|
92
|
+
allowedRedirectUris?: AllowedRedirectUris,
|
|
93
|
+
alwaysAllowed: (string | undefined)[] = []
|
|
94
|
+
): boolean {
|
|
95
|
+
// No allow-list configured: unchanged (permissive) behaviour
|
|
96
|
+
if (allowedRedirectUris === undefined) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Relative paths can only land on the plugin's own origin
|
|
101
|
+
if (isRelativeRedirect(redirectUri)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const entry of alwaysAllowed) {
|
|
106
|
+
if (entry && matchesEntry(redirectUri, entry)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (typeof allowedRedirectUris === "function") {
|
|
112
|
+
return allowedRedirectUris(redirectUri, provider);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return allowedRedirectUris.some((entry) => matchesEntry(redirectUri, entry));
|
|
116
|
+
}
|