@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,304 @@
|
|
|
1
|
+
import {catchAsyncError, catchError} from "@tsed/core";
|
|
2
|
+
import {runInContext} from "@tsed/di";
|
|
3
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
4
|
+
|
|
5
|
+
import {OidcInteractionContext} from "./OidcInteractionContext.js";
|
|
6
|
+
import {OidcProvider} from "./OidcProvider.js";
|
|
7
|
+
|
|
8
|
+
async function createOidcInteractionContextFixture(grantId: any = "grantId") {
|
|
9
|
+
const $ctx = PlatformTest.createRequestContext();
|
|
10
|
+
|
|
11
|
+
const session = {
|
|
12
|
+
accountId: "accountId"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const interactionDetails: any = {
|
|
16
|
+
uid: "uid",
|
|
17
|
+
grantId,
|
|
18
|
+
session,
|
|
19
|
+
prompt: {
|
|
20
|
+
name: "login"
|
|
21
|
+
},
|
|
22
|
+
params: {
|
|
23
|
+
client_id: "client_id",
|
|
24
|
+
email: "email@email.com"
|
|
25
|
+
},
|
|
26
|
+
save: vi.fn()
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const oidcProvider = {
|
|
30
|
+
interactionDetails: vi.fn().mockResolvedValue(interactionDetails),
|
|
31
|
+
interactionFinished: vi.fn().mockResolvedValue(undefined),
|
|
32
|
+
interactionResult: vi.fn().mockResolvedValue(undefined),
|
|
33
|
+
setProviderSession: vi.fn().mockResolvedValue(undefined),
|
|
34
|
+
find: vi.fn().mockResolvedValue("grant"),
|
|
35
|
+
Grant: class {
|
|
36
|
+
static find = vi.fn().mockResolvedValue("grant");
|
|
37
|
+
},
|
|
38
|
+
Client: {
|
|
39
|
+
find: vi.fn().mockResolvedValue({
|
|
40
|
+
client_id: "client_id"
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
Account: {
|
|
44
|
+
findAccount: vi.fn().mockResolvedValue({
|
|
45
|
+
accountId: "accountId"
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const oidcCtx = await PlatformTest.invoke<OidcInteractionContext>(OidcInteractionContext, [
|
|
50
|
+
{
|
|
51
|
+
token: OidcProvider,
|
|
52
|
+
use: {
|
|
53
|
+
get() {
|
|
54
|
+
return oidcProvider;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
await runInContext($ctx, () => oidcCtx.interactionDetails());
|
|
61
|
+
|
|
62
|
+
return {$ctx, oidcCtx, oidcProvider, session, interactionDetails};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("OidcInteractionContext", () => {
|
|
66
|
+
beforeEach(() => PlatformTest.create());
|
|
67
|
+
afterEach(() => PlatformTest.reset());
|
|
68
|
+
|
|
69
|
+
describe("uid()", () => {
|
|
70
|
+
it("should return uid", async () => {
|
|
71
|
+
const {$ctx, oidcCtx} = await createOidcInteractionContextFixture();
|
|
72
|
+
|
|
73
|
+
await runInContext($ctx, () => {
|
|
74
|
+
expect(oidcCtx.uid).toEqual("uid");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("checkInteractionName()", () => {
|
|
80
|
+
it("should throw error", async () => {
|
|
81
|
+
const {$ctx, oidcCtx} = await createOidcInteractionContextFixture();
|
|
82
|
+
|
|
83
|
+
await runInContext($ctx, () => {
|
|
84
|
+
const error: any = catchError(() => oidcCtx.checkInteractionName("test"));
|
|
85
|
+
|
|
86
|
+
expect(error?.message).toEqual("Bad interaction name");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("checkClientId()", () => {
|
|
92
|
+
it("should throw error", async () => {
|
|
93
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
94
|
+
oidcProvider.Client.find.mockResolvedValue(undefined);
|
|
95
|
+
|
|
96
|
+
await runInContext($ctx, async () => {
|
|
97
|
+
const error: any = await catchAsyncError(() => oidcCtx.checkClientId());
|
|
98
|
+
|
|
99
|
+
expect(error?.message).toEqual("Unknown client_id client_id");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("grantId()", () => {
|
|
105
|
+
it("should return uid", async () => {
|
|
106
|
+
const {$ctx, oidcCtx} = await createOidcInteractionContextFixture();
|
|
107
|
+
await runInContext($ctx, () => {
|
|
108
|
+
expect(oidcCtx.grantId).toEqual("grantId");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("session()", () => {
|
|
114
|
+
it("should return session", async () => {
|
|
115
|
+
const {$ctx, oidcCtx, interactionDetails} = await createOidcInteractionContextFixture();
|
|
116
|
+
await runInContext($ctx, () => {
|
|
117
|
+
expect(oidcCtx.session).toEqual(interactionDetails.session);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("prompt()", () => {
|
|
123
|
+
it("should return prompt", async () => {
|
|
124
|
+
const {$ctx, oidcCtx} = await createOidcInteractionContextFixture();
|
|
125
|
+
|
|
126
|
+
await runInContext($ctx, () => {
|
|
127
|
+
expect(oidcCtx.prompt).toEqual({
|
|
128
|
+
name: "login"
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("params()", () => {
|
|
135
|
+
it("should return params", async () => {
|
|
136
|
+
const {$ctx, oidcCtx, interactionDetails} = await createOidcInteractionContextFixture();
|
|
137
|
+
await runInContext($ctx, () => {
|
|
138
|
+
expect(oidcCtx.params).toEqual(interactionDetails.params);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("interactionFinished()", () => {
|
|
144
|
+
it("should return call interactionFinished", async () => {
|
|
145
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
146
|
+
|
|
147
|
+
await runInContext($ctx, async () => {
|
|
148
|
+
await oidcCtx.interactionFinished({login: {accountId: "string"}}, {mergeWithLastSubmission: false});
|
|
149
|
+
|
|
150
|
+
expect(oidcProvider.interactionFinished).toHaveBeenCalledWith(
|
|
151
|
+
$ctx.getReq(),
|
|
152
|
+
$ctx.getRes(),
|
|
153
|
+
{login: {accountId: "string"}},
|
|
154
|
+
{mergeWithLastSubmission: false}
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("interactionResult()", () => {
|
|
161
|
+
it("should return call interactionResult", async () => {
|
|
162
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
163
|
+
await runInContext($ctx, async () => {
|
|
164
|
+
await oidcCtx.interactionResult({login: {accountId: "string"}}, {mergeWithLastSubmission: false});
|
|
165
|
+
|
|
166
|
+
expect(oidcProvider.interactionResult).toHaveBeenCalledWith(
|
|
167
|
+
$ctx.getReq(),
|
|
168
|
+
$ctx.getRes(),
|
|
169
|
+
{login: {accountId: "string"}},
|
|
170
|
+
{mergeWithLastSubmission: false}
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
it("should return call interactionResult (default)", async () => {
|
|
175
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
176
|
+
await runInContext($ctx, async () => {
|
|
177
|
+
await oidcCtx.interactionResult({login: {accountId: "string"}});
|
|
178
|
+
|
|
179
|
+
expect(oidcProvider.interactionResult).toHaveBeenCalledWith(
|
|
180
|
+
$ctx.getReq(),
|
|
181
|
+
$ctx.getRes(),
|
|
182
|
+
{login: {accountId: "string"}},
|
|
183
|
+
{mergeWithLastSubmission: false}
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("render()", () => {
|
|
190
|
+
it("should return call render", async () => {
|
|
191
|
+
const {$ctx, oidcCtx} = await createOidcInteractionContextFixture();
|
|
192
|
+
await runInContext($ctx, async () => {
|
|
193
|
+
vi.spyOn($ctx.response, "render").mockResolvedValue("");
|
|
194
|
+
|
|
195
|
+
await oidcCtx.render("login", {});
|
|
196
|
+
|
|
197
|
+
expect($ctx.response.render).toHaveBeenCalledWith("login", {});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("save()", () => {
|
|
203
|
+
it("should return call save", async () => {
|
|
204
|
+
const {$ctx, oidcCtx, interactionDetails} = await createOidcInteractionContextFixture();
|
|
205
|
+
await runInContext($ctx, async () => {
|
|
206
|
+
await oidcCtx.save(2000);
|
|
207
|
+
|
|
208
|
+
expect(interactionDetails.save).toHaveBeenCalledWith(2000);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should return call save (default)", async () => {
|
|
213
|
+
const {$ctx, oidcCtx, interactionDetails} = await createOidcInteractionContextFixture();
|
|
214
|
+
await runInContext($ctx, async () => {
|
|
215
|
+
await oidcCtx.save(100);
|
|
216
|
+
|
|
217
|
+
expect(interactionDetails.save).toHaveBeenCalledWith(100);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("findClient()", () => {
|
|
223
|
+
it("should return call findClient", async () => {
|
|
224
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
225
|
+
await runInContext($ctx, async () => {
|
|
226
|
+
const result = await oidcCtx.findClient("client_id");
|
|
227
|
+
|
|
228
|
+
expect(result).toEqual({
|
|
229
|
+
client_id: "client_id"
|
|
230
|
+
});
|
|
231
|
+
expect(oidcProvider.Client.find).toHaveBeenCalledWith("client_id");
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should return call findClient (default)", async () => {
|
|
236
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
237
|
+
|
|
238
|
+
await runInContext($ctx, async () => {
|
|
239
|
+
const result = await oidcCtx.findClient();
|
|
240
|
+
|
|
241
|
+
expect(result).toEqual({
|
|
242
|
+
client_id: "client_id"
|
|
243
|
+
});
|
|
244
|
+
expect(oidcProvider.Client.find).toHaveBeenCalledWith("client_id");
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("findAccount()", () => {
|
|
250
|
+
it("should return call findAccount", async () => {
|
|
251
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
252
|
+
await runInContext($ctx, async () => {
|
|
253
|
+
const result = await oidcCtx.findAccount(undefined, "token");
|
|
254
|
+
|
|
255
|
+
expect(oidcProvider.Account.findAccount).toHaveBeenCalledWith(undefined, "accountId", "token");
|
|
256
|
+
expect(result).toEqual({
|
|
257
|
+
accountId: "accountId"
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should return call findAccount (with accountId)", async () => {
|
|
263
|
+
const {$ctx, oidcCtx, oidcProvider} = await createOidcInteractionContextFixture();
|
|
264
|
+
await runInContext($ctx, async () => {
|
|
265
|
+
const result = await oidcCtx.findAccount("accountId", "token");
|
|
266
|
+
|
|
267
|
+
expect(oidcProvider.Account.findAccount).toHaveBeenCalledWith(undefined, "accountId", "token");
|
|
268
|
+
expect(result).toEqual({
|
|
269
|
+
accountId: "accountId"
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should return call findAccount (without session/accountId)", async () => {
|
|
275
|
+
const {$ctx, oidcCtx, interactionDetails} = await createOidcInteractionContextFixture();
|
|
276
|
+
|
|
277
|
+
await runInContext($ctx, async () => {
|
|
278
|
+
interactionDetails.session.accountId = undefined;
|
|
279
|
+
|
|
280
|
+
const result = await runInContext($ctx, () => oidcCtx.findAccount(undefined, "token"));
|
|
281
|
+
|
|
282
|
+
expect(result).toBeUndefined();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("getGrant()", () => {
|
|
288
|
+
it("should return call grant from grantId", async () => {
|
|
289
|
+
const {$ctx, oidcCtx} = await createOidcInteractionContextFixture();
|
|
290
|
+
await runInContext($ctx, async () => {
|
|
291
|
+
const result = await oidcCtx.getGrant();
|
|
292
|
+
expect(result).toEqual("grant");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("should create grant", async () => {
|
|
297
|
+
const {$ctx, oidcCtx} = await createOidcInteractionContextFixture(null);
|
|
298
|
+
await runInContext($ctx, async () => {
|
|
299
|
+
const result = await oidcCtx.getGrant();
|
|
300
|
+
expect(result).toEqual({});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {Env} from "@tsed/core";
|
|
2
|
+
import {constant, context, inject, Injectable} from "@tsed/di";
|
|
3
|
+
import {Unauthorized} from "@tsed/exceptions";
|
|
4
|
+
import {PlatformContext} from "@tsed/platform-http";
|
|
5
|
+
import omit from "lodash/omit.js";
|
|
6
|
+
import type {Account, default as Provider, InteractionResults, PromptDetail} from "oidc-provider";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
INTERACTION_CONTEXT,
|
|
10
|
+
INTERACTION_DETAILS,
|
|
11
|
+
INTERACTION_GRANT_ID,
|
|
12
|
+
INTERACTION_PARAMS,
|
|
13
|
+
INTERACTION_PROMPT,
|
|
14
|
+
INTERACTION_SESSION,
|
|
15
|
+
INTERACTION_UID
|
|
16
|
+
} from "../constants/constants.js";
|
|
17
|
+
import {OidcSession} from "../decorators/oidcSession.js";
|
|
18
|
+
import {OidcClient, OidcInteraction} from "../domain/interfaces.js";
|
|
19
|
+
import {OidcBadInteractionName} from "../domain/OidcBadInteractionName.js";
|
|
20
|
+
import {OidcInteractionPromptProps} from "../domain/OidcInteractionPromptProps.js";
|
|
21
|
+
import {debug} from "../utils/debug.js";
|
|
22
|
+
import {OidcInteractions} from "./OidcInteractions.js";
|
|
23
|
+
import {OidcProvider} from "./OidcProvider.js";
|
|
24
|
+
|
|
25
|
+
@Injectable()
|
|
26
|
+
export class OidcInteractionContext {
|
|
27
|
+
protected env = constant<Env>("env");
|
|
28
|
+
protected oidcProvider = inject(OidcProvider);
|
|
29
|
+
protected oidcInteractions = inject(OidcInteractions);
|
|
30
|
+
|
|
31
|
+
get $ctx() {
|
|
32
|
+
return context<PlatformContext>();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get raw(): OidcInteraction {
|
|
36
|
+
return this.$ctx.get(INTERACTION_DETAILS)!;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get session(): OidcSession | undefined {
|
|
40
|
+
return this.raw.session as any;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get prompt(): PromptDetail {
|
|
44
|
+
return this.raw.prompt;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get params(): Record<string, any> {
|
|
48
|
+
return this.raw.params;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get uid(): string {
|
|
52
|
+
return this.raw.uid;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get grantId(): string {
|
|
56
|
+
return (this.raw as any).grantId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async runInteraction(name?: string) {
|
|
60
|
+
name = name || this.prompt.name;
|
|
61
|
+
|
|
62
|
+
const handler = this.oidcInteractions.getInteractionHandler(name);
|
|
63
|
+
|
|
64
|
+
if (handler) {
|
|
65
|
+
this.raw.prompt = {
|
|
66
|
+
...this.raw.prompt,
|
|
67
|
+
name,
|
|
68
|
+
reasons: [name]
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
await handler(this.$ctx);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async interactionDetails(): Promise<OidcInteraction> {
|
|
76
|
+
const raw = await this.oidcProvider.get().interactionDetails(this.$ctx.getReq(), this.$ctx.getRes());
|
|
77
|
+
const {uid, prompt, params, session, grantId} = raw as any;
|
|
78
|
+
|
|
79
|
+
this.$ctx.set(INTERACTION_CONTEXT, this);
|
|
80
|
+
this.$ctx.set(INTERACTION_DETAILS, raw);
|
|
81
|
+
this.$ctx.set(INTERACTION_UID, uid);
|
|
82
|
+
this.$ctx.set(INTERACTION_PROMPT, prompt);
|
|
83
|
+
this.$ctx.set(INTERACTION_PARAMS, params);
|
|
84
|
+
this.$ctx.set(INTERACTION_GRANT_ID, grantId);
|
|
85
|
+
this.$ctx.set(INTERACTION_SESSION, session);
|
|
86
|
+
|
|
87
|
+
return raw;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interactionFinished(
|
|
91
|
+
result: InteractionResults,
|
|
92
|
+
options: {
|
|
93
|
+
mergeWithLastSubmission?: boolean;
|
|
94
|
+
} = {mergeWithLastSubmission: false}
|
|
95
|
+
) {
|
|
96
|
+
return this.oidcProvider.get().interactionFinished(this.$ctx.getReq(), this.$ctx.getRes(), result, options);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interactionResult(
|
|
100
|
+
result: InteractionResults,
|
|
101
|
+
options: {
|
|
102
|
+
mergeWithLastSubmission?: boolean;
|
|
103
|
+
} = {mergeWithLastSubmission: false}
|
|
104
|
+
) {
|
|
105
|
+
return this.oidcProvider.get().interactionResult(this.$ctx.getReq(), this.$ctx.getRes(), result, options);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async interactionPrompt({client, ...options}: Record<string, any>): Promise<OidcInteractionPromptProps> {
|
|
109
|
+
client = client || (await this.findClient());
|
|
110
|
+
|
|
111
|
+
const omitClientProps = constant("oidc.render.omitClientProps", []);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
client: omit(client, ["clientSecret", ...omitClientProps]),
|
|
115
|
+
uid: this.uid,
|
|
116
|
+
grantId: this.grantId,
|
|
117
|
+
details: this.prompt.details,
|
|
118
|
+
params: {
|
|
119
|
+
...this.params,
|
|
120
|
+
...options.params
|
|
121
|
+
},
|
|
122
|
+
...options,
|
|
123
|
+
...this.debug()
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
render(view: string, result: any): Promise<string> {
|
|
128
|
+
return this.$ctx.response.render(view, result);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
save(ttl: number): Promise<string> {
|
|
132
|
+
return this.raw.save(ttl);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
findClient(clientId: string = this.params.client_id): Promise<OidcClient | undefined> {
|
|
136
|
+
const key = `$client:${clientId}`;
|
|
137
|
+
|
|
138
|
+
return this.$ctx.cacheAsync(key, () => this.oidcProvider.get().Client.find(clientId));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
findAccount(sub?: string, token?: any): Promise<Account | undefined> {
|
|
142
|
+
if (!sub && this.session) {
|
|
143
|
+
sub = this.session?.accountId as any;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!sub) {
|
|
147
|
+
return Promise.resolve(undefined);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const key = `$account:${sub}`;
|
|
151
|
+
|
|
152
|
+
return this.$ctx.cacheAsync<Account | undefined>(key, (() => {
|
|
153
|
+
return this.oidcProvider.get().Account.findAccount(undefined as any, sub!, token);
|
|
154
|
+
}) as any);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getGrant(): Promise<InstanceType<Provider["Grant"]>> {
|
|
158
|
+
const {Grant} = this.oidcProvider.get() as any;
|
|
159
|
+
|
|
160
|
+
if (this.grantId) {
|
|
161
|
+
// we'll be modifying existing grant in existing session
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
return Grant.find(this.grantId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return Promise.resolve(
|
|
167
|
+
new Grant({
|
|
168
|
+
accountId: this.session?.accountId,
|
|
169
|
+
clientId: this.params.client_id
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
checkInteractionName(name: string) {
|
|
175
|
+
if (this.prompt.name !== name) {
|
|
176
|
+
throw new OidcBadInteractionName("Bad interaction name");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async checkClientId(clientId = this.params.client_id) {
|
|
181
|
+
const client = await this.findClient(clientId);
|
|
182
|
+
|
|
183
|
+
if (!client) {
|
|
184
|
+
throw new Unauthorized(`Unknown client_id ${clientId}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
debug(obj?: any): any {
|
|
189
|
+
/* istanbul ignore next */
|
|
190
|
+
if (this.env === Env.PROD) {
|
|
191
|
+
return {session: undefined, dbg: {params: undefined, prompt: undefined}};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (obj) {
|
|
195
|
+
return debug(obj);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
session: this.session ? this.debug(this.session) : undefined,
|
|
200
|
+
dbg: {
|
|
201
|
+
params: this.debug(this.params),
|
|
202
|
+
prompt: this.debug(this.prompt)
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {Env} from "@tsed/core";
|
|
2
|
+
import {constant, Injectable, injector, Provider, TokenProvider} from "@tsed/di";
|
|
3
|
+
import {PlatformContext, PlatformHandler} from "@tsed/platform-http";
|
|
4
|
+
import {EndpointMetadata} from "@tsed/schema";
|
|
5
|
+
|
|
6
|
+
import {INTERACTION, INTERACTION_OPTIONS, INTERACTIONS} from "../constants/constants.js";
|
|
7
|
+
import {OidcInteractionOptions} from "../domain/OidcInteractionOptions.js";
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class OidcInteractions {
|
|
11
|
+
protected injector = injector();
|
|
12
|
+
protected env = constant<Env>("env");
|
|
13
|
+
protected interactions: Map<string, Provider> = new Map();
|
|
14
|
+
|
|
15
|
+
$onInit(): void {
|
|
16
|
+
const platformHandler = this.injector.get<PlatformHandler>(PlatformHandler)!;
|
|
17
|
+
|
|
18
|
+
this.getInteractions().forEach((provider: Provider) => {
|
|
19
|
+
const {name} = provider.store.get<OidcInteractionOptions>(INTERACTION_OPTIONS);
|
|
20
|
+
this.interactions.set(name, provider);
|
|
21
|
+
|
|
22
|
+
if (this.injector.get(provider.token)?.$prompt) {
|
|
23
|
+
provider.store.set("$prompt", platformHandler.createCustomHandler(provider, "$prompt"));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getInteractions(): Provider[] {
|
|
29
|
+
const interactionsProvider = this.injector.getProviders().find((provider) => provider.subType === INTERACTIONS);
|
|
30
|
+
|
|
31
|
+
/* istanbul ignore next */
|
|
32
|
+
if (!interactionsProvider) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return interactionsProvider.children
|
|
37
|
+
.map((token: TokenProvider) => this.injector.getProvider(token)!)
|
|
38
|
+
.filter((provider: Provider) => provider?.subType === INTERACTION);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getInteractionProvider(name: string): Provider | undefined {
|
|
42
|
+
return this.interactions.get(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getInteractionHandler(name: string) {
|
|
46
|
+
const interaction = this.getInteractionProvider(name);
|
|
47
|
+
|
|
48
|
+
if (interaction) {
|
|
49
|
+
const endpoint = EndpointMetadata.get(interaction.useClass, "$prompt");
|
|
50
|
+
return (ctx: PlatformContext) => {
|
|
51
|
+
// Add current endpoint metadata to ctx
|
|
52
|
+
ctx.endpoint = endpoint;
|
|
53
|
+
return interaction.store.get("$prompt")(ctx);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {constant, Injectable} from "@tsed/di";
|
|
2
|
+
import {getJwks, JwksKeyParameters} from "@tsed/jwks";
|
|
3
|
+
import {join} from "path";
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class OidcJwks {
|
|
7
|
+
public jwksPath: string = constant("oidc.jwksPath", join(process.cwd(), "keys", "jwks.json"));
|
|
8
|
+
public certificates?: JwksKeyParameters[] = constant("oidc.certificates");
|
|
9
|
+
public keys: string;
|
|
10
|
+
|
|
11
|
+
$onInit() {
|
|
12
|
+
return this.getJwks();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getJwks() {
|
|
16
|
+
return getJwks({
|
|
17
|
+
path: this.jwksPath,
|
|
18
|
+
certificates: this.certificates
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|