@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.
Files changed (49) hide show
  1. package/package.json +16 -15
  2. package/src/OidcModule.spec.ts +116 -0
  3. package/src/OidcModule.ts +70 -0
  4. package/src/constants/constants.ts +10 -0
  5. package/src/decorators/grantId.spec.ts +17 -0
  6. package/src/decorators/grantId.ts +10 -0
  7. package/src/decorators/interaction.spec.ts +27 -0
  8. package/src/decorators/interaction.ts +18 -0
  9. package/src/decorators/interactions.spec.ts +23 -0
  10. package/src/decorators/interactions.ts +21 -0
  11. package/src/decorators/noCache.ts +7 -0
  12. package/src/decorators/oidcCtx.spec.ts +17 -0
  13. package/src/decorators/oidcCtx.ts +11 -0
  14. package/src/decorators/oidcSession.spec.ts +17 -0
  15. package/src/decorators/oidcSession.ts +14 -0
  16. package/src/decorators/params.spec.ts +17 -0
  17. package/src/decorators/params.ts +10 -0
  18. package/src/decorators/prompt.spec.ts +17 -0
  19. package/src/decorators/prompt.ts +11 -0
  20. package/src/decorators/uid.spec.ts +17 -0
  21. package/src/decorators/uid.ts +10 -0
  22. package/src/domain/InteractionMethods.ts +11 -0
  23. package/src/domain/OidcAccountsMethods.ts +10 -0
  24. package/src/domain/OidcBadInteractionName.ts +3 -0
  25. package/src/domain/OidcInteractionMethods.ts +3 -0
  26. package/src/domain/OidcInteractionOptions.ts +8 -0
  27. package/src/domain/OidcInteractionPromptProps.ts +11 -0
  28. package/src/domain/OidcSettings.ts +72 -0
  29. package/src/domain/interfaces.ts +13 -0
  30. package/src/index.ts +33 -0
  31. package/src/middlewares/OidcInteractionMiddleware.spec.ts +40 -0
  32. package/src/middlewares/OidcInteractionMiddleware.ts +14 -0
  33. package/src/middlewares/OidcNoCacheMiddleware.spec.ts +18 -0
  34. package/src/middlewares/OidcNoCacheMiddleware.ts +10 -0
  35. package/src/middlewares/OidcSecureMiddleware.spec.ts +106 -0
  36. package/src/middlewares/OidcSecureMiddleware.ts +29 -0
  37. package/src/services/OidcAdapters.spec.ts +100 -0
  38. package/src/services/OidcAdapters.ts +92 -0
  39. package/src/services/OidcInteractionContext.spec.ts +304 -0
  40. package/src/services/OidcInteractionContext.ts +206 -0
  41. package/src/services/OidcInteractions.ts +57 -0
  42. package/src/services/OidcJwks.ts +21 -0
  43. package/src/services/OidcPolicy.spec.ts +156 -0
  44. package/src/services/OidcPolicy.ts +92 -0
  45. package/src/services/OidcProvider.spec.ts +116 -0
  46. package/src/services/OidcProvider.ts +198 -0
  47. package/src/utils/debug.spec.ts +12 -0
  48. package/src/utils/debug.ts +25 -0
  49. package/src/utils/events.ts +61 -0
@@ -0,0 +1,72 @@
1
+ import type {Adapter} from "@tsed/adapters";
2
+ import type {Type} from "@tsed/core";
3
+ import type {JwksKeyParameters} from "@tsed/jwks";
4
+ import type {Configuration} from "oidc-provider";
5
+
6
+ import type {OidcAccountsMethods} from "./OidcAccountsMethods.js";
7
+
8
+ export interface OidcSettings extends Configuration {
9
+ /**
10
+ * force the secure cookie. By default, in dev mode it's disabled and in production it's enabled.
11
+ */
12
+ secureCookies?: boolean;
13
+ /**
14
+ * Path on which the oidc-provider instance is mounted.
15
+ */
16
+ path?: string;
17
+ /**
18
+ * Issuer URI. By default, Ts.ED create issuer with http://localhost:${httpPort}
19
+ */
20
+ issuer?: string;
21
+ /**
22
+ * Path to store jwks keys.
23
+ */
24
+ jwksPath?: string;
25
+ /**
26
+ * Generate jwks from given certificates
27
+ */
28
+ certificates?: JwksKeyParameters[];
29
+ /**
30
+ * Secure keys.
31
+ */
32
+ secureKey?: string[];
33
+ /**
34
+ * Enable proxy.
35
+ */
36
+ proxy?: boolean;
37
+ /**
38
+ * Allow redirect_uri on HTTP protocol and localhost domain.
39
+ */
40
+ allowHttpLocalhost?: boolean;
41
+ /**
42
+ * Injectable service to manage accounts.
43
+ */
44
+ Accounts?: Type<OidcAccountsMethods>;
45
+ /**
46
+ * Injectable adapter to manage database connexion.
47
+ */
48
+ Adapter?: Type<Adapter>;
49
+ /**
50
+ * Use the connection name for the OIDCRedisAdapter.
51
+ */
52
+ connectionName?: string;
53
+
54
+ plugins?: TsED.OIDCPluginSettings;
55
+
56
+ render?: {
57
+ /**
58
+ * By default ["clientSecret"] is omitted
59
+ */
60
+ omitClientProps?: string[];
61
+ };
62
+ }
63
+
64
+ declare global {
65
+ namespace TsED {
66
+ interface OIDCPluginSettings {}
67
+
68
+ interface Configuration {
69
+ oidc: OidcSettings;
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,13 @@
1
+ import type {default as Provider, interactionPolicy} from "oidc-provider";
2
+
3
+ export type OIDCContext = InstanceType<Provider["OIDCContext"]>;
4
+ export type OidcClient = InstanceType<Provider["Client"]>;
5
+ export type DefaultPolicy = interactionPolicy.DefaultPolicy;
6
+ export type AuthorizationCode = InstanceType<Provider["AuthorizationCode"]>;
7
+ export type AccessToken = InstanceType<Provider["AccessToken"]>;
8
+ export type ClientCredentials = InstanceType<Provider["ClientCredentials"]>;
9
+ export type DeviceCode = InstanceType<Provider["DeviceCode"]>;
10
+ export type RefreshToken = InstanceType<Provider["RefreshToken"]>;
11
+ export type BackchannelAuthenticationRequest = InstanceType<Provider["BackchannelAuthenticationRequest"]>;
12
+ export type Grant = InstanceType<Provider["Grant"]>;
13
+ export type OidcInteraction = InstanceType<Provider["Interaction"]>;
package/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./constants/constants.js";
5
+ export * from "./decorators/grantId.js";
6
+ export * from "./decorators/interaction.js";
7
+ export * from "./decorators/interactions.js";
8
+ export * from "./decorators/noCache.js";
9
+ export * from "./decorators/oidcCtx.js";
10
+ export * from "./decorators/oidcSession.js";
11
+ export * from "./decorators/params.js";
12
+ export * from "./decorators/prompt.js";
13
+ export * from "./decorators/uid.js";
14
+ export * from "./domain/InteractionMethods.js";
15
+ export * from "./domain/interfaces.js";
16
+ export * from "./domain/OidcAccountsMethods.js";
17
+ export * from "./domain/OidcBadInteractionName.js";
18
+ export * from "./domain/OidcInteractionMethods.js";
19
+ export * from "./domain/OidcInteractionOptions.js";
20
+ export * from "./domain/OidcInteractionPromptProps.js";
21
+ export * from "./domain/OidcSettings.js";
22
+ export * from "./middlewares/OidcInteractionMiddleware.js";
23
+ export * from "./middlewares/OidcNoCacheMiddleware.js";
24
+ export * from "./middlewares/OidcSecureMiddleware.js";
25
+ export * from "./OidcModule.js";
26
+ export * from "./services/OidcAdapters.js";
27
+ export * from "./services/OidcInteractionContext.js";
28
+ export * from "./services/OidcInteractions.js";
29
+ export * from "./services/OidcJwks.js";
30
+ export * from "./services/OidcPolicy.js";
31
+ export * from "./services/OidcProvider.js";
32
+ export * from "./utils/debug.js";
33
+ export * from "./utils/events.js";
@@ -0,0 +1,40 @@
1
+ import {faker} from "@faker-js/faker";
2
+ import {PlatformTest} from "@tsed/platform-http/testing";
3
+
4
+ import {
5
+ INTERACTION_CONTEXT,
6
+ INTERACTION_DETAILS,
7
+ INTERACTION_PARAMS,
8
+ INTERACTION_PROMPT,
9
+ INTERACTION_SESSION,
10
+ INTERACTION_UID
11
+ } from "../constants/constants.js";
12
+ import {OidcInteractionContext} from "../services/OidcInteractionContext.js";
13
+ import {OidcInteractionMiddleware} from "./OidcInteractionMiddleware.js";
14
+
15
+ describe("OidcInteractionMiddleware", () => {
16
+ beforeEach(() => PlatformTest.create());
17
+ afterEach(() => PlatformTest.reset());
18
+ it("should create interaction details and store it to the context", async () => {
19
+ const interactionDetails = {
20
+ uid: faker.string.uuid(),
21
+ prompt: {},
22
+ params: {},
23
+ session: {}
24
+ };
25
+ const oidcInteractionContext = {
26
+ interactionDetails: vi.fn().mockReturnValue(interactionDetails)
27
+ };
28
+
29
+ const middleware = await PlatformTest.invoke<OidcInteractionMiddleware>(OidcInteractionMiddleware, [
30
+ {
31
+ token: OidcInteractionContext,
32
+ use: oidcInteractionContext
33
+ }
34
+ ]);
35
+
36
+ await middleware.use();
37
+
38
+ expect(oidcInteractionContext.interactionDetails).toHaveBeenCalledWith();
39
+ });
40
+ });
@@ -0,0 +1,14 @@
1
+ import {Inject} from "@tsed/di";
2
+ import {Middleware} from "@tsed/platform-middlewares";
3
+
4
+ import {OidcInteractionContext} from "../services/OidcInteractionContext.js";
5
+
6
+ @Middleware()
7
+ export class OidcInteractionMiddleware {
8
+ @Inject()
9
+ protected oidcInteractionContext: OidcInteractionContext;
10
+
11
+ async use() {
12
+ await this.oidcInteractionContext.interactionDetails();
13
+ }
14
+ }
@@ -0,0 +1,18 @@
1
+ import {PlatformTest} from "@tsed/platform-http/testing";
2
+
3
+ import {OidcNoCacheMiddleware} from "./OidcNoCacheMiddleware.js";
4
+
5
+ describe("OidcNoCacheMiddleware", () => {
6
+ beforeEach(() => PlatformTest.create());
7
+ afterEach(() => PlatformTest.reset());
8
+ it("should add headers", () => {
9
+ const middleware = PlatformTest.get<OidcNoCacheMiddleware>(OidcNoCacheMiddleware);
10
+ const ctx = PlatformTest.createRequestContext();
11
+ vi.spyOn(ctx.response, "setHeader").mockReturnThis();
12
+
13
+ middleware.use(ctx);
14
+
15
+ expect(ctx.response.setHeader).toHaveBeenCalledWith("Pragma", "no-cache");
16
+ expect(ctx.response.setHeader).toHaveBeenCalledWith("Cache-Control", "no-cache, no-store");
17
+ });
18
+ });
@@ -0,0 +1,10 @@
1
+ import {Middleware, MiddlewareMethods} from "@tsed/platform-middlewares";
2
+ import {Context} from "@tsed/platform-params";
3
+
4
+ @Middleware()
5
+ export class OidcNoCacheMiddleware implements MiddlewareMethods {
6
+ use(@Context() ctx: Context) {
7
+ ctx.response.setHeader("Pragma", "no-cache");
8
+ ctx.response.setHeader("Cache-Control", "no-cache, no-store");
9
+ }
10
+ }
@@ -0,0 +1,106 @@
1
+ import {PlatformTest} from "@tsed/platform-http/testing";
2
+
3
+ import {OidcSecureMiddleware} from "./OidcSecureMiddleware.js";
4
+
5
+ describe("OidcSecureMiddleware", () => {
6
+ beforeEach(() => PlatformTest.create());
7
+ afterEach(() => PlatformTest.reset());
8
+
9
+ it("should check if the request is not secure on GET verb", async () => {
10
+ const middleware = await PlatformTest.invoke<OidcSecureMiddleware>(OidcSecureMiddleware);
11
+ const request = PlatformTest.createRequest({
12
+ secure: false,
13
+ method: "GET",
14
+ url: "/path",
15
+ headers: {
16
+ host: "host"
17
+ }
18
+ });
19
+
20
+ const ctx = PlatformTest.createRequestContext({
21
+ event: {
22
+ request
23
+ }
24
+ });
25
+ vi.spyOn(ctx.response, "redirect").mockReturnValue(undefined as any);
26
+
27
+ middleware.use(ctx);
28
+
29
+ expect(ctx.response.redirect).toHaveBeenCalledWith(302, "https://host/path");
30
+ });
31
+
32
+ it("should check if the request is not secure on HEAD verb", async () => {
33
+ const middleware = await PlatformTest.invoke<OidcSecureMiddleware>(OidcSecureMiddleware);
34
+ const ctx = PlatformTest.createRequestContext({
35
+ event: {
36
+ request: PlatformTest.createRequest({
37
+ secure: false,
38
+ method: "GET",
39
+ url: "/path",
40
+ headers: {
41
+ host: "host"
42
+ }
43
+ })
44
+ }
45
+ });
46
+
47
+ vi.spyOn(ctx.response, "redirect").mockReturnValue(undefined as any);
48
+
49
+ middleware.use(ctx);
50
+
51
+ expect(ctx.response.redirect).toHaveBeenCalledWith(302, "https://host/path");
52
+ });
53
+
54
+ it("should check if the request is not secure on POST verb", async () => {
55
+ const middleware = await PlatformTest.invoke<OidcSecureMiddleware>(OidcSecureMiddleware);
56
+ const ctx = PlatformTest.createRequestContext({
57
+ event: {
58
+ request: PlatformTest.createRequest({
59
+ secure: false,
60
+ method: "POST",
61
+ url: "/path",
62
+ headers: {
63
+ host: "host"
64
+ }
65
+ })
66
+ }
67
+ });
68
+
69
+ let actualError: any;
70
+ try {
71
+ middleware.use(ctx);
72
+ } catch (er) {
73
+ actualError = er;
74
+ }
75
+
76
+ expect(actualError.status).toEqual(400);
77
+ expect(actualError.message).toEqual("InvalidRequest");
78
+ expect(actualError.body).toEqual({
79
+ error: "invalid_request",
80
+ error_description: "Do yourself a favor and only use https"
81
+ });
82
+ });
83
+
84
+ it("should check if the request is secure on GET verb", async () => {
85
+ const middleware = await PlatformTest.invoke<OidcSecureMiddleware>(OidcSecureMiddleware);
86
+
87
+ const ctx = PlatformTest.createRequestContext({
88
+ event: {
89
+ request: PlatformTest.createRequest({
90
+ secure: true,
91
+ method: "GET",
92
+ url: "/path",
93
+ headers: {
94
+ host: "host"
95
+ }
96
+ })
97
+ }
98
+ });
99
+
100
+ vi.spyOn(ctx.response, "redirect").mockReturnValue(undefined as any);
101
+
102
+ middleware.use(ctx);
103
+
104
+ expect(ctx.response.redirect).not.toHaveBeenCalled();
105
+ });
106
+ });
@@ -0,0 +1,29 @@
1
+ import {BadRequest} from "@tsed/exceptions";
2
+ import {Middleware, MiddlewareMethods} from "@tsed/platform-middlewares";
3
+ import {Context} from "@tsed/platform-params";
4
+ import url from "url";
5
+
6
+ @Middleware()
7
+ export class OidcSecureMiddleware implements MiddlewareMethods {
8
+ use(@Context() ctx: Context) {
9
+ const req = ctx.request;
10
+
11
+ if (!req.secure) {
12
+ if (req.method === "GET" || req.method === "HEAD") {
13
+ ctx.response.redirect(
14
+ 302,
15
+ url.format({
16
+ protocol: "https",
17
+ host: req.get("host"),
18
+ pathname: req.url
19
+ })
20
+ );
21
+ } else {
22
+ throw new BadRequest("InvalidRequest", {
23
+ error: "invalid_request",
24
+ error_description: "Do yourself a favor and only use https"
25
+ });
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,100 @@
1
+ import {faker} from "@faker-js/faker";
2
+ import {PlatformTest} from "@tsed/platform-http/testing";
3
+ import type {Adapter} from "oidc-provider";
4
+
5
+ import {OidcAdapters} from "./OidcAdapters.js";
6
+
7
+ describe("OidcAdapters", () => {
8
+ beforeEach(() => PlatformTest.create());
9
+ afterEach(() => PlatformTest.reset());
10
+
11
+ describe("createAdapterClass()", () => {
12
+ let adapter: Adapter;
13
+
14
+ beforeEach(async () => {
15
+ const oidcAdapters = await PlatformTest.invoke<OidcAdapters>(OidcAdapters);
16
+ adapter = new (oidcAdapters.createAdapterClass())("clients");
17
+ });
18
+
19
+ describe("adapter.upsert()", () => {
20
+ it("should call upsert", async () => {
21
+ const id = faker.string.uuid();
22
+
23
+ await adapter.upsert(
24
+ id,
25
+ {
26
+ client_id: id
27
+ },
28
+ 20000
29
+ );
30
+
31
+ const obj: any = await adapter.find(id);
32
+
33
+ expect(obj._id).toEqual(id);
34
+ expect(obj.client_id).toEqual(id);
35
+ expect(obj.expires_at).toBeInstanceOf(Date);
36
+
37
+ await adapter.destroy(id);
38
+ });
39
+ });
40
+
41
+ describe("adapter.findByUserCode()", () => {
42
+ it("should find data by userCode", async () => {
43
+ const id = faker.string.uuid();
44
+
45
+ await adapter.upsert(
46
+ id,
47
+ {
48
+ userCode: id
49
+ },
50
+ 20000
51
+ );
52
+
53
+ const obj: any = await adapter.findByUserCode(id);
54
+
55
+ expect(obj._id).toEqual(id);
56
+ expect(obj.userCode).toEqual(id);
57
+ expect(obj.expires_at).toBeInstanceOf(Date);
58
+ });
59
+ });
60
+ describe("adapter.findByUid()", () => {
61
+ it("should find data by uid", async () => {
62
+ const id = faker.string.uuid();
63
+
64
+ await adapter.upsert(
65
+ id,
66
+ {
67
+ uid: id
68
+ },
69
+ 20000
70
+ );
71
+
72
+ const obj: any = await adapter.findByUid(id);
73
+
74
+ expect(obj._id).toEqual(id);
75
+ expect(obj.uid).toEqual(id);
76
+ expect(obj.expires_at).toBeInstanceOf(Date);
77
+ });
78
+ });
79
+ describe("adapter.deleteMany()", () => {
80
+ it("should delete items", async () => {
81
+ const id = faker.string.uuid();
82
+
83
+ await adapter.upsert(
84
+ id,
85
+ {
86
+ grantId: id
87
+ },
88
+ 20000
89
+ );
90
+
91
+ await adapter.consume(id);
92
+ const obj: any = await adapter.find(id);
93
+ await adapter.revokeByGrantId(id);
94
+
95
+ expect(obj._id).toEqual(id);
96
+ expect(obj.grantId).toEqual(id);
97
+ });
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,92 @@
1
+ import {Adapter, Adapters} from "@tsed/adapters";
2
+ import {constant, inject, Injectable} from "@tsed/di";
3
+ import type {Adapter as OidcAdapter, AdapterConstructor} from "oidc-provider";
4
+
5
+ export type OidcAdapterMethods<Model = any> = Adapter<Model> & Partial<Omit<OidcAdapter, "upsert">>;
6
+
7
+ @Injectable()
8
+ export class OidcAdapters {
9
+ protected adapters = inject(Adapters);
10
+
11
+ createAdapterClass(): AdapterConstructor {
12
+ const self = this;
13
+ const adapterBase = constant("oidc.Adapter", constant("adapters.Adapter"));
14
+ const connectionName = constant("oidc.connectionName", "default");
15
+
16
+ return class CustomAdapter implements OidcAdapter {
17
+ adapter: OidcAdapterMethods;
18
+
19
+ constructor(name: string) {
20
+ this.adapter = self.adapters.invokeAdapter<any>({
21
+ adapter: adapterBase,
22
+ collectionName: name,
23
+ connectionName,
24
+ model: Object
25
+ }) as OidcAdapterMethods;
26
+ }
27
+
28
+ async upsert(id: string, payload: any, expiresIn: number): Promise<void> {
29
+ let expiresAt;
30
+
31
+ if (expiresIn) {
32
+ expiresAt = new Date(Date.now() + expiresIn * 1000);
33
+ }
34
+
35
+ await this.adapter.upsert(id, payload, expiresAt);
36
+ }
37
+
38
+ find(id: string) {
39
+ return this.adapter.findById(id);
40
+ }
41
+
42
+ findByUserCode(userCode: string) {
43
+ // istanbul ignore next
44
+ if (this.adapter.findByUserCode) {
45
+ return this.adapter.findByUserCode(userCode);
46
+ }
47
+
48
+ return this.adapter.findOne({
49
+ userCode
50
+ });
51
+ }
52
+
53
+ findByUid(uid: string) {
54
+ // istanbul ignore next
55
+ if (this.adapter.findByUid) {
56
+ return this.adapter.findByUid(uid);
57
+ }
58
+
59
+ return this.adapter.findOne({
60
+ uid
61
+ });
62
+ }
63
+
64
+ async destroy(id: string) {
65
+ // istanbul ignore next
66
+ if (this.adapter.destroy) {
67
+ return this.adapter.destroy(id);
68
+ }
69
+
70
+ await this.adapter.deleteById(id);
71
+ }
72
+
73
+ async revokeByGrantId(grantId: string) {
74
+ // istanbul ignore next
75
+ if (this.adapter.revokeByGrantId) {
76
+ return this.adapter.revokeByGrantId(grantId);
77
+ }
78
+
79
+ await this.adapter.deleteMany({grantId});
80
+ }
81
+
82
+ async consume(grantId: string) {
83
+ // istanbul ignore next
84
+ if (this.adapter.consume) {
85
+ return this.adapter.consume(grantId);
86
+ }
87
+
88
+ await this.adapter.update(grantId, {consumed: Math.floor(Date.now() / 1000)});
89
+ }
90
+ };
91
+ }
92
+ }