@tsed/oidc-provider 8.0.1 → 8.0.3
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/package.json +16 -15
- package/src/OidcModule.spec.ts +116 -0
- package/src/OidcModule.ts +70 -0
- package/src/constants/constants.ts +10 -0
- package/src/decorators/grantId.spec.ts +17 -0
- package/src/decorators/grantId.ts +10 -0
- package/src/decorators/interaction.spec.ts +27 -0
- package/src/decorators/interaction.ts +18 -0
- package/src/decorators/interactions.spec.ts +23 -0
- package/src/decorators/interactions.ts +21 -0
- package/src/decorators/noCache.ts +7 -0
- package/src/decorators/oidcCtx.spec.ts +17 -0
- package/src/decorators/oidcCtx.ts +11 -0
- package/src/decorators/oidcSession.spec.ts +17 -0
- package/src/decorators/oidcSession.ts +14 -0
- package/src/decorators/params.spec.ts +17 -0
- package/src/decorators/params.ts +10 -0
- package/src/decorators/prompt.spec.ts +17 -0
- package/src/decorators/prompt.ts +11 -0
- package/src/decorators/uid.spec.ts +17 -0
- package/src/decorators/uid.ts +10 -0
- package/src/domain/InteractionMethods.ts +11 -0
- package/src/domain/OidcAccountsMethods.ts +10 -0
- package/src/domain/OidcBadInteractionName.ts +3 -0
- package/src/domain/OidcInteractionMethods.ts +3 -0
- package/src/domain/OidcInteractionOptions.ts +8 -0
- package/src/domain/OidcInteractionPromptProps.ts +11 -0
- package/src/domain/OidcSettings.ts +72 -0
- package/src/domain/interfaces.ts +13 -0
- package/src/index.ts +33 -0
- package/src/middlewares/OidcInteractionMiddleware.spec.ts +40 -0
- package/src/middlewares/OidcInteractionMiddleware.ts +14 -0
- package/src/middlewares/OidcNoCacheMiddleware.spec.ts +18 -0
- package/src/middlewares/OidcNoCacheMiddleware.ts +10 -0
- package/src/middlewares/OidcSecureMiddleware.spec.ts +106 -0
- package/src/middlewares/OidcSecureMiddleware.ts +29 -0
- package/src/services/OidcAdapters.spec.ts +100 -0
- package/src/services/OidcAdapters.ts +92 -0
- package/src/services/OidcInteractionContext.spec.ts +304 -0
- package/src/services/OidcInteractionContext.ts +206 -0
- package/src/services/OidcInteractions.ts +57 -0
- package/src/services/OidcJwks.ts +21 -0
- package/src/services/OidcPolicy.spec.ts +156 -0
- package/src/services/OidcPolicy.ts +92 -0
- package/src/services/OidcProvider.spec.ts +116 -0
- package/src/services/OidcProvider.ts +198 -0
- package/src/utils/debug.spec.ts +12 -0
- package/src/utils/debug.ts +25 -0
- package/src/utils/events.ts +61 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {Env} from "@tsed/core";
|
|
2
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
3
|
+
|
|
4
|
+
import {ConsentInteraction} from "../../test/app/interactions/ConsentInteraction.js";
|
|
5
|
+
import {Interaction} from "../decorators/interaction.js";
|
|
6
|
+
import {OidcInteractions} from "./OidcInteractions.js";
|
|
7
|
+
import {OidcPolicy} from "./OidcPolicy.js";
|
|
8
|
+
|
|
9
|
+
describe("OidcPolicy", () => {
|
|
10
|
+
beforeEach(() =>
|
|
11
|
+
PlatformTest.create({
|
|
12
|
+
env: Env.PROD,
|
|
13
|
+
oidc: {
|
|
14
|
+
issuer: "http://localhost:8081",
|
|
15
|
+
secureKey: ["secureKey"]
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
);
|
|
19
|
+
afterEach(() => PlatformTest.reset());
|
|
20
|
+
describe("createPrompt()", () => {
|
|
21
|
+
it("should bind options to prompt instance", async () => {
|
|
22
|
+
const oidcProvider = PlatformTest.get<OidcPolicy>(OidcPolicy);
|
|
23
|
+
const instance = {};
|
|
24
|
+
const options = {
|
|
25
|
+
name: "name",
|
|
26
|
+
requestable: true,
|
|
27
|
+
details: vi.fn(),
|
|
28
|
+
checks: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const prompt = await oidcProvider.createPrompt(instance, options);
|
|
32
|
+
|
|
33
|
+
expect(prompt.details).toEqual(options.details);
|
|
34
|
+
expect(prompt.name).toEqual(options.name);
|
|
35
|
+
expect(prompt.requestable).toEqual(options.requestable);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should bind methods from instance to prompt instance", async () => {
|
|
39
|
+
const oidcProvider = PlatformTest.get<OidcPolicy>(OidcPolicy);
|
|
40
|
+
const instance = {
|
|
41
|
+
details: vi.fn(),
|
|
42
|
+
checks: vi.fn().mockReturnValue([])
|
|
43
|
+
};
|
|
44
|
+
const options = {
|
|
45
|
+
name: "name",
|
|
46
|
+
requestable: true
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const prompt = await oidcProvider.createPrompt(instance, options);
|
|
50
|
+
|
|
51
|
+
expect(prompt.details).toBeDefined();
|
|
52
|
+
expect(prompt.name).toEqual(options.name);
|
|
53
|
+
expect(prompt.requestable).toEqual(options.requestable);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("getPolicy()", () => {
|
|
57
|
+
describe('when there is interactions with "priority" property', () => {
|
|
58
|
+
@Interaction({
|
|
59
|
+
name: "test"
|
|
60
|
+
})
|
|
61
|
+
class TestInteraction {}
|
|
62
|
+
|
|
63
|
+
@Interaction({
|
|
64
|
+
name: "test2"
|
|
65
|
+
})
|
|
66
|
+
class Test2Interaction {}
|
|
67
|
+
|
|
68
|
+
@Interaction({
|
|
69
|
+
name: "login"
|
|
70
|
+
})
|
|
71
|
+
class LoginInteraction {}
|
|
72
|
+
|
|
73
|
+
@Interaction({
|
|
74
|
+
name: "consent"
|
|
75
|
+
})
|
|
76
|
+
class ConsentInteraction {}
|
|
77
|
+
|
|
78
|
+
@Interaction({
|
|
79
|
+
name: "test3",
|
|
80
|
+
priority: 0
|
|
81
|
+
})
|
|
82
|
+
class Test3Interaction {}
|
|
83
|
+
|
|
84
|
+
it("should load policy (without priority)", async () => {
|
|
85
|
+
const oidcInteractions = {
|
|
86
|
+
getInteractions: vi
|
|
87
|
+
.fn()
|
|
88
|
+
.mockReturnValue([
|
|
89
|
+
PlatformTest.injector.getProvider(Test2Interaction),
|
|
90
|
+
PlatformTest.injector.getProvider(LoginInteraction),
|
|
91
|
+
PlatformTest.injector.getProvider(ConsentInteraction),
|
|
92
|
+
PlatformTest.injector.getProvider(TestInteraction)
|
|
93
|
+
])
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const oidcProvider = await PlatformTest.invoke<OidcPolicy>(OidcPolicy, [
|
|
97
|
+
{
|
|
98
|
+
token: OidcInteractions,
|
|
99
|
+
use: oidcInteractions
|
|
100
|
+
}
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const policy = oidcProvider.getPolicy();
|
|
104
|
+
|
|
105
|
+
expect(policy.map(({name}: {name: string}) => name)).toEqual(["test2", "login", "consent", "test"]);
|
|
106
|
+
});
|
|
107
|
+
it("should load policy (with priority)", async () => {
|
|
108
|
+
const oidcInteractions = {
|
|
109
|
+
getInteractions: vi
|
|
110
|
+
.fn()
|
|
111
|
+
.mockReturnValue([
|
|
112
|
+
PlatformTest.injector.getProvider(Test2Interaction),
|
|
113
|
+
PlatformTest.injector.getProvider(LoginInteraction),
|
|
114
|
+
PlatformTest.injector.getProvider(ConsentInteraction),
|
|
115
|
+
PlatformTest.injector.getProvider(TestInteraction),
|
|
116
|
+
PlatformTest.injector.getProvider(Test3Interaction)
|
|
117
|
+
])
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const oidcProvider = await PlatformTest.invoke<OidcPolicy>(OidcPolicy, [
|
|
121
|
+
{
|
|
122
|
+
token: OidcInteractions,
|
|
123
|
+
use: oidcInteractions
|
|
124
|
+
}
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const policy = oidcProvider.getPolicy();
|
|
128
|
+
|
|
129
|
+
expect(policy.map(({name}: {name: string}) => name)).toEqual(["test3", "login", "consent", "test2", "test"]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe("when there is no interactions without usePriority", () => {
|
|
133
|
+
it("should load policy", async () => {
|
|
134
|
+
const oidcInteractions = {
|
|
135
|
+
getInteractions: vi.fn().mockReturnValue([])
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const oidcPolicy = await PlatformTest.invoke<OidcPolicy>(OidcPolicy, [
|
|
139
|
+
{
|
|
140
|
+
token: OidcInteractions,
|
|
141
|
+
use: oidcInteractions
|
|
142
|
+
}
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
vi.spyOn(oidcPolicy as any, "getInteractions").mockReturnValue({
|
|
146
|
+
usePriority: false,
|
|
147
|
+
interactions: new Map([["login", {name: "login", instance: {}} as any]])
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const policy = oidcPolicy.getPolicy();
|
|
151
|
+
|
|
152
|
+
expect(policy.map(({name}: {name: string}) => name)).toEqual(["login", "consent"]);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {inject, Injectable, injector, Provider} from "@tsed/di";
|
|
2
|
+
import {interactionPolicy} from "oidc-provider";
|
|
3
|
+
|
|
4
|
+
import {InteractionMethods} from "../domain/InteractionMethods.js";
|
|
5
|
+
import {OidcInteractionOptions} from "../domain/OidcInteractionOptions.js";
|
|
6
|
+
import {OidcInteractions} from "./OidcInteractions.js";
|
|
7
|
+
import Prompt = interactionPolicy.Prompt;
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class OidcPolicy {
|
|
11
|
+
protected injector = injector();
|
|
12
|
+
protected oidcInteractions = inject(OidcInteractions);
|
|
13
|
+
|
|
14
|
+
public getPolicy() {
|
|
15
|
+
let policy = interactionPolicy.base();
|
|
16
|
+
const {usePriority, interactions} = this.getInteractions();
|
|
17
|
+
|
|
18
|
+
if (interactions.size) {
|
|
19
|
+
for (const {name, instance, options} of interactions.values()) {
|
|
20
|
+
if (!policy.get(name)) {
|
|
21
|
+
const prompt = this.createPrompt(instance, options);
|
|
22
|
+
|
|
23
|
+
policy.add(prompt, options.priority);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (instance.$onCreate) {
|
|
27
|
+
instance.$onCreate(policy.get(name)!);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// reordering interactions by interactions index
|
|
32
|
+
if (!usePriority) {
|
|
33
|
+
policy = policy.sort((a, b) => {
|
|
34
|
+
const o1 = interactions.get(a.name)?.order || 0;
|
|
35
|
+
const o2 = interactions.get(b.name)?.order || 0;
|
|
36
|
+
|
|
37
|
+
return o1 < o2 ? -1 : 1;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return this.injector.alter("$alterOidcPolicy", policy);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public createPrompt(instance: InteractionMethods, options: OidcInteractionOptions): Prompt {
|
|
46
|
+
const {checks: originalChecks = [], details, ...promptOptions} = options;
|
|
47
|
+
const checks = [...(instance.checks ? instance.checks() : originalChecks)].filter(Boolean);
|
|
48
|
+
|
|
49
|
+
return new interactionPolicy.Prompt(promptOptions, instance.details ? instance.details.bind(instance) : details, ...checks);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private getInteractions() {
|
|
53
|
+
let usePriority = false;
|
|
54
|
+
|
|
55
|
+
const interactions = this.oidcInteractions.getInteractions();
|
|
56
|
+
|
|
57
|
+
const map = interactions.reduce(
|
|
58
|
+
(map, provider, index) => {
|
|
59
|
+
const instance = this.injector.get<InteractionMethods>(provider.token)!;
|
|
60
|
+
|
|
61
|
+
const options = provider.store.get("interactionOptions");
|
|
62
|
+
|
|
63
|
+
if (options.priority !== undefined) {
|
|
64
|
+
usePriority = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return map.set(options.name, {
|
|
68
|
+
order: index,
|
|
69
|
+
name: options.name,
|
|
70
|
+
provider,
|
|
71
|
+
instance,
|
|
72
|
+
options
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
new Map<
|
|
76
|
+
string,
|
|
77
|
+
{
|
|
78
|
+
order: number;
|
|
79
|
+
provider: Provider;
|
|
80
|
+
instance: any;
|
|
81
|
+
options: OidcInteractionOptions;
|
|
82
|
+
name: string;
|
|
83
|
+
}
|
|
84
|
+
>()
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
interactions: map,
|
|
89
|
+
usePriority
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import "../../test/app/controllers/oidc/InteractionsCtrl.js";
|
|
2
|
+
|
|
3
|
+
import {Env} from "@tsed/core";
|
|
4
|
+
import {runInContext} from "@tsed/di";
|
|
5
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
6
|
+
|
|
7
|
+
import {OidcProvider} from "./OidcProvider.js";
|
|
8
|
+
|
|
9
|
+
describe("OidcProvider", () => {
|
|
10
|
+
describe("Production", () => {
|
|
11
|
+
beforeEach(() =>
|
|
12
|
+
PlatformTest.create({
|
|
13
|
+
env: Env.PROD,
|
|
14
|
+
oidc: {
|
|
15
|
+
issuer: "http://localhost:8081",
|
|
16
|
+
secureKey: ["secureKey"]
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
afterEach(() => PlatformTest.reset());
|
|
21
|
+
|
|
22
|
+
it("should create oidc instance", () => {
|
|
23
|
+
const oidcProvider = PlatformTest.get<OidcProvider>(OidcProvider);
|
|
24
|
+
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
expect(oidcProvider.getInteractionsUrl()({}, {uid: "uid"})).toEqual("/interaction/uid");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("createErrorHandler()", () => {
|
|
30
|
+
beforeEach(() =>
|
|
31
|
+
PlatformTest.create({
|
|
32
|
+
env: Env.PROD,
|
|
33
|
+
oidc: {
|
|
34
|
+
issuer: "http://localhost:8081",
|
|
35
|
+
secureKey: ["secureKey"]
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
afterEach(() => PlatformTest.reset());
|
|
40
|
+
|
|
41
|
+
it("should intercept all oidc errors", () => {
|
|
42
|
+
const oidcProvider = PlatformTest.get<OidcProvider>(OidcProvider);
|
|
43
|
+
vi.spyOn((oidcProvider as any).injector.logger, "error");
|
|
44
|
+
|
|
45
|
+
const fn = (oidcProvider as any).createErrorHandler("event");
|
|
46
|
+
fn(
|
|
47
|
+
{
|
|
48
|
+
headers: {
|
|
49
|
+
origin: "origin"
|
|
50
|
+
},
|
|
51
|
+
oidc: {
|
|
52
|
+
params: {
|
|
53
|
+
client_id: "client_id"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{error: "error", error_description: "error_description", error_detail: "error_detail"},
|
|
58
|
+
"account_id",
|
|
59
|
+
"sid"
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect((oidcProvider as any).injector.logger.error).toHaveBeenCalledWith({
|
|
63
|
+
duration: expect.any(Number),
|
|
64
|
+
reqId: expect.any(String),
|
|
65
|
+
account_id: "account_id",
|
|
66
|
+
error: {error_description: "error_description", error_detail: "error_detail", error: "error"},
|
|
67
|
+
event: "OIDC_ERROR",
|
|
68
|
+
headers: {
|
|
69
|
+
origin: "origin"
|
|
70
|
+
},
|
|
71
|
+
params: {client_id: "client_id"},
|
|
72
|
+
sid: "sid",
|
|
73
|
+
type: "event",
|
|
74
|
+
time: expect.any(Date)
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
it("should intercept all oidc errors (in request context)", async () => {
|
|
78
|
+
const oidcProvider = PlatformTest.get<OidcProvider>(OidcProvider);
|
|
79
|
+
const ctx = PlatformTest.createRequestContext();
|
|
80
|
+
|
|
81
|
+
vi.spyOn(ctx.logger, "error");
|
|
82
|
+
|
|
83
|
+
const fn = (oidcProvider as any).createErrorHandler("event");
|
|
84
|
+
|
|
85
|
+
await runInContext(ctx, () => {
|
|
86
|
+
fn(
|
|
87
|
+
{
|
|
88
|
+
headers: {
|
|
89
|
+
origin: "origin"
|
|
90
|
+
},
|
|
91
|
+
oidc: {
|
|
92
|
+
params: {
|
|
93
|
+
client_id: "client_id"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{error: "error", error_description: "error_description", error_detail: "error_detail"},
|
|
98
|
+
"account_id",
|
|
99
|
+
"sid"
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(ctx.logger.error).toHaveBeenCalledWith({
|
|
104
|
+
account_id: "account_id",
|
|
105
|
+
error: {error_description: "error_description", error_detail: "error_detail", error: "error"},
|
|
106
|
+
event: "OIDC_ERROR",
|
|
107
|
+
headers: {
|
|
108
|
+
origin: "origin"
|
|
109
|
+
},
|
|
110
|
+
params: {client_id: "client_id"},
|
|
111
|
+
sid: "sid",
|
|
112
|
+
type: "event"
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {Env, setValue} from "@tsed/core";
|
|
2
|
+
import {constant, context, inject, Injectable, InjectorService} from "@tsed/di";
|
|
3
|
+
import {PlatformApplication, PlatformContext} from "@tsed/platform-http";
|
|
4
|
+
import Provider, {type Configuration, type KoaContextWithOIDC} from "oidc-provider";
|
|
5
|
+
|
|
6
|
+
import {INTERACTIONS} from "../constants/constants.js";
|
|
7
|
+
import {OidcAccountsMethods} from "../domain/OidcAccountsMethods.js";
|
|
8
|
+
import {OidcSettings} from "../domain/OidcSettings.js";
|
|
9
|
+
import {OIDC_ERROR_EVENTS} from "../utils/events.js";
|
|
10
|
+
import {OidcAdapters} from "./OidcAdapters.js";
|
|
11
|
+
import {OidcJwks} from "./OidcJwks.js";
|
|
12
|
+
import {OidcPolicy} from "./OidcPolicy.js";
|
|
13
|
+
|
|
14
|
+
function mapError(error: any) {
|
|
15
|
+
return Object.getOwnPropertyNames(error).reduce((obj: any, key) => {
|
|
16
|
+
return {
|
|
17
|
+
...obj,
|
|
18
|
+
[key]: error[key]
|
|
19
|
+
};
|
|
20
|
+
}, {});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Injectable()
|
|
24
|
+
export class OidcProvider {
|
|
25
|
+
raw: Provider;
|
|
26
|
+
|
|
27
|
+
protected env = constant<Env>("env");
|
|
28
|
+
protected httpPort = constant<number | string>("httpPort");
|
|
29
|
+
protected httpsPort = constant<number | string>("httpsPort");
|
|
30
|
+
protected issuer = constant<string>("oidc.issuer", "");
|
|
31
|
+
protected oidc = constant<OidcSettings>("oidc")!;
|
|
32
|
+
protected platformName = constant<string>("PLATFORM_NAME");
|
|
33
|
+
protected oidcJwks = inject(OidcJwks);
|
|
34
|
+
protected oidcPolicy = inject(OidcPolicy);
|
|
35
|
+
protected adapters = inject(OidcAdapters);
|
|
36
|
+
protected injector = inject(InjectorService);
|
|
37
|
+
protected app = inject(PlatformApplication);
|
|
38
|
+
|
|
39
|
+
get logger() {
|
|
40
|
+
return this.$ctx.logger;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected get $ctx() {
|
|
44
|
+
return context<PlatformContext>();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
hasConfiguration() {
|
|
48
|
+
return !!this.oidc;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getConfiguration(): Promise<Configuration> {
|
|
52
|
+
const [jwks, adapter] = await Promise.all([this.oidcJwks.getJwks(), this.adapters.createAdapterClass()]);
|
|
53
|
+
const {
|
|
54
|
+
issuer,
|
|
55
|
+
jwksPath,
|
|
56
|
+
secureKey,
|
|
57
|
+
proxy,
|
|
58
|
+
Accounts,
|
|
59
|
+
secureCookies = this.env == Env.PROD,
|
|
60
|
+
Adapter,
|
|
61
|
+
connectionName,
|
|
62
|
+
render,
|
|
63
|
+
...options
|
|
64
|
+
} = this.oidc;
|
|
65
|
+
|
|
66
|
+
const configuration: Configuration = {
|
|
67
|
+
interactions: {
|
|
68
|
+
/* istanbul ignore next */
|
|
69
|
+
url: (ctx, interaction) => `interaction/${interaction.uid}`
|
|
70
|
+
},
|
|
71
|
+
...options,
|
|
72
|
+
adapter,
|
|
73
|
+
jwks
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (Accounts) {
|
|
77
|
+
configuration.findAccount = (ctx, id, token) => this.injector.get<OidcAccountsMethods>(Accounts)!.findAccount(id, token);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (secureCookies) {
|
|
81
|
+
setValue(configuration, "cookies.short.secure", true);
|
|
82
|
+
setValue(configuration, "cookies.long.secure", true);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const policy = this.oidcPolicy.getPolicy();
|
|
86
|
+
|
|
87
|
+
if (policy) {
|
|
88
|
+
setValue(configuration, "interactions.policy", policy);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = this.getInteractionsUrl();
|
|
92
|
+
if (url) {
|
|
93
|
+
setValue(configuration, "interactions.url", url);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return configuration;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getIssuer() {
|
|
100
|
+
if (this.issuer) {
|
|
101
|
+
return this.issuer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// istanbul ignore next
|
|
105
|
+
if (this.httpsPort) {
|
|
106
|
+
return `https://localhost:${this.httpsPort}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return `http://localhost:${this.httpPort}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get(): Provider {
|
|
113
|
+
return this.raw;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a new instance of OidcProvider
|
|
118
|
+
*/
|
|
119
|
+
async create(): Promise<void | Provider> {
|
|
120
|
+
const {proxy = this.env === Env.PROD, secureKey, allowHttpLocalhost = this.env !== Env.PROD} = this.oidc;
|
|
121
|
+
const configuration = await this.getConfiguration();
|
|
122
|
+
|
|
123
|
+
await this.injector.alterAsync("$alterOidcConfiguration", configuration);
|
|
124
|
+
|
|
125
|
+
const oidcProvider = new Provider(this.getIssuer(), configuration);
|
|
126
|
+
|
|
127
|
+
if (proxy) {
|
|
128
|
+
// istanbul ignore next
|
|
129
|
+
switch (this.platformName) {
|
|
130
|
+
default:
|
|
131
|
+
case "express":
|
|
132
|
+
oidcProvider.proxy = true;
|
|
133
|
+
break;
|
|
134
|
+
case "koa":
|
|
135
|
+
(this.app.rawApp as any).proxy = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (secureKey) {
|
|
141
|
+
oidcProvider.app.keys = secureKey;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.raw = oidcProvider;
|
|
145
|
+
|
|
146
|
+
if (allowHttpLocalhost) {
|
|
147
|
+
this.allowHttpLocalhost();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
OIDC_ERROR_EVENTS.map((event) => {
|
|
151
|
+
this.raw.on(event, this.createErrorHandler(event));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await this.injector.emit("$onCreateOIDC", this.raw);
|
|
155
|
+
|
|
156
|
+
return this.raw;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private createErrorHandler(event: string) {
|
|
160
|
+
return (ctx: KoaContextWithOIDC, error: any, accountId?: string, sid?: string) => {
|
|
161
|
+
this.logger.error({
|
|
162
|
+
event: "OIDC_ERROR",
|
|
163
|
+
type: event,
|
|
164
|
+
error: mapError(error),
|
|
165
|
+
account_id: accountId,
|
|
166
|
+
params: ctx.oidc.params,
|
|
167
|
+
headers: ctx.headers,
|
|
168
|
+
sid
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// TODO see if we need to call platformExceptions
|
|
172
|
+
// this.platformExceptions.catch(error, ctx.request.$ctx);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private getInteractionsUrl() {
|
|
177
|
+
const provider = this.injector.getProviders().find((provider) => provider.subType === INTERACTIONS);
|
|
178
|
+
|
|
179
|
+
if (provider) {
|
|
180
|
+
return (ctx: any, interaction: any) => {
|
|
181
|
+
return provider.path.replace(/:uid/, interaction.uid);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private allowHttpLocalhost() {
|
|
187
|
+
const {invalidate: orig} = (this.raw.Client as any).Schema.prototype;
|
|
188
|
+
|
|
189
|
+
(this.raw.Client as any).Schema.prototype.invalidate = function invalidate(message: string, code: string) {
|
|
190
|
+
if (code === "implicit-force-https" || code === "implicit-forbid-localhost") {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* istanbul ignore next */
|
|
195
|
+
return orig.call(this, message);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {isEmpty} from "@tsed/core";
|
|
2
|
+
import qs from "querystring";
|
|
3
|
+
import {inspect} from "util";
|
|
4
|
+
|
|
5
|
+
const keys = new Set();
|
|
6
|
+
|
|
7
|
+
function serialize(obj: any) {
|
|
8
|
+
return Object.entries(obj).reduce((acc: any, [key, value]) => {
|
|
9
|
+
keys.add(key);
|
|
10
|
+
|
|
11
|
+
if (isEmpty(value)) {
|
|
12
|
+
return acc;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
acc[key] = inspect(value, {depth: null});
|
|
16
|
+
return acc;
|
|
17
|
+
}, {});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const debug = (obj: any) =>
|
|
21
|
+
qs.stringify(serialize(obj), "<br/>", ": ", {
|
|
22
|
+
encodeURIComponent(value) {
|
|
23
|
+
return keys.has(value) ? `<strong>${value}</strong>` : value;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exports all OIDC providers events
|
|
3
|
+
*/
|
|
4
|
+
export const OIDC_EVENTS = [
|
|
5
|
+
"access_token.destroyed",
|
|
6
|
+
"access_token.saved",
|
|
7
|
+
"access_token.issued",
|
|
8
|
+
"authorization_code.consumed",
|
|
9
|
+
"authorization_code.destroyed",
|
|
10
|
+
"authorization_code.saved",
|
|
11
|
+
"authorization.accepted",
|
|
12
|
+
"authorization.error",
|
|
13
|
+
"authorization.success",
|
|
14
|
+
"backchannel.error",
|
|
15
|
+
"backchannel.success",
|
|
16
|
+
"jwks.error",
|
|
17
|
+
"client_credentials.destroyed",
|
|
18
|
+
"client_credentials.saved",
|
|
19
|
+
"client_credentials.issued",
|
|
20
|
+
"device_code.consumed",
|
|
21
|
+
"device_code.destroyed",
|
|
22
|
+
"device_code.saved",
|
|
23
|
+
"discovery.error",
|
|
24
|
+
"end_session.error",
|
|
25
|
+
"end_session.success",
|
|
26
|
+
"grant.error",
|
|
27
|
+
"grant.revoked",
|
|
28
|
+
"grant.success",
|
|
29
|
+
"initial_access_token.destroyed",
|
|
30
|
+
"initial_access_token.saved",
|
|
31
|
+
"interaction.destroyed",
|
|
32
|
+
"interaction.ended",
|
|
33
|
+
"interaction.saved",
|
|
34
|
+
"interaction.started",
|
|
35
|
+
"introspection.error",
|
|
36
|
+
"replay_detection.destroyed",
|
|
37
|
+
"replay_detection.saved",
|
|
38
|
+
"pushed_authorization_request.error",
|
|
39
|
+
"pushed_authorization_request.success",
|
|
40
|
+
"pushed_authorization_request.destroyed",
|
|
41
|
+
"pushed_authorization_request.saved",
|
|
42
|
+
"refresh_token.consumed",
|
|
43
|
+
"refresh_token.destroyed",
|
|
44
|
+
"refresh_token.saved",
|
|
45
|
+
"registration_access_token.destroyed",
|
|
46
|
+
"registration_access_token.saved",
|
|
47
|
+
"registration_create.error",
|
|
48
|
+
"registration_create.success",
|
|
49
|
+
"registration_delete.error",
|
|
50
|
+
"registration_delete.success",
|
|
51
|
+
"registration_read.error",
|
|
52
|
+
"registration_update.error",
|
|
53
|
+
"registration_update.success",
|
|
54
|
+
"revocation.error",
|
|
55
|
+
"server_error",
|
|
56
|
+
"session.destroyed",
|
|
57
|
+
"session.saved",
|
|
58
|
+
"userinfo.error"
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export const OIDC_ERROR_EVENTS = OIDC_EVENTS.filter((e) => e.includes("error"));
|