@ballistix.digital/identity-connector 0.0.0-local
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 +6 -0
- package/README.md +631 -0
- package/dist/common/domain/enum/identityConnectorExceptionType.enum.d.ts +14 -0
- package/dist/common/domain/enum/identityConnectorExceptionType.enum.js +18 -0
- package/dist/common/domain/model/identity.d.ts +9 -0
- package/dist/common/domain/model/identity.js +2 -0
- package/dist/common/domain/model/identityProfile.d.ts +6 -0
- package/dist/common/domain/model/identityProfile.js +2 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +23 -0
- package/dist/module/entra/domain/model/entraProviderOptions.d.ts +6 -0
- package/dist/module/entra/domain/model/entraProviderOptions.js +42 -0
- package/dist/module/entra/index.d.ts +2 -0
- package/dist/module/entra/index.js +7 -0
- package/dist/module/entra/service/entra.provider.d.ts +23 -0
- package/dist/module/entra/service/entra.provider.js +72 -0
- package/dist/module/identity/domain/exception/identityConnectorException.d.ts +5 -0
- package/dist/module/identity/domain/exception/identityConnectorException.js +48 -0
- package/dist/module/identity/domain/model/identityConnectorModuleOptions.d.ts +7 -0
- package/dist/module/identity/domain/model/identityConnectorModuleOptions.js +2 -0
- package/dist/module/identity/domain/model/identityProvider.d.ts +22 -0
- package/dist/module/identity/domain/model/identityProvider.js +17 -0
- package/dist/module/identity/domain/model/identityProviderOptions.d.ts +5 -0
- package/dist/module/identity/domain/model/identityProviderOptions.js +34 -0
- package/dist/module/identity/domain/model/identityProvidersToken.d.ts +1 -0
- package/dist/module/identity/domain/model/identityProvidersToken.js +4 -0
- package/dist/module/identity/domain/model/userStore.d.ts +10 -0
- package/dist/module/identity/domain/model/userStore.js +12 -0
- package/dist/module/identity/domain/validation/default.decorator.d.ts +1 -0
- package/dist/module/identity/domain/validation/default.decorator.js +10 -0
- package/dist/module/identity/domain/validation/trimmed.decorator.d.ts +2 -0
- package/dist/module/identity/domain/validation/trimmed.decorator.js +17 -0
- package/dist/module/identity/domain/validation/validateOptions.d.ts +2 -0
- package/dist/module/identity/domain/validation/validateOptions.js +44 -0
- package/dist/module/identity/identityConnector.module.d.ts +6 -0
- package/dist/module/identity/identityConnector.module.js +47 -0
- package/dist/module/identity/service/identityAuth.guard.d.ts +20 -0
- package/dist/module/identity/service/identityAuth.guard.js +117 -0
- package/dist/module/identity/service/identityManagement.d.ts +16 -0
- package/dist/module/identity/service/identityManagement.js +54 -0
- package/dist/module/identity/service/identityRuntime.d.ts +2 -0
- package/dist/module/identity/service/identityRuntime.js +6 -0
- package/dist/module/identity/service/identityRuntimeInternals.d.ts +11 -0
- package/dist/module/identity/service/identityRuntimeInternals.js +52 -0
- package/dist/module/identity/service/signIn.cache.d.ts +20 -0
- package/dist/module/identity/service/signIn.cache.js +102 -0
- package/dist/module/jwt/service/signedToken.verifier.d.ts +12 -0
- package/dist/module/jwt/service/signedToken.verifier.js +56 -0
- package/dist/module/zitadel/domain/model/zitadelManagementOptions.d.ts +4 -0
- package/dist/module/zitadel/domain/model/zitadelManagementOptions.js +31 -0
- package/dist/module/zitadel/domain/model/zitadelProviderOptions.d.ts +10 -0
- package/dist/module/zitadel/domain/model/zitadelProviderOptions.js +48 -0
- package/dist/module/zitadel/domain/model/zitadelServiceUserKey.d.ts +6 -0
- package/dist/module/zitadel/domain/model/zitadelServiceUserKey.js +42 -0
- package/dist/module/zitadel/domain/validation/isRsaPrivateKey.decorator.d.ts +2 -0
- package/dist/module/zitadel/domain/validation/isRsaPrivateKey.decorator.js +26 -0
- package/dist/module/zitadel/domain/validation/organizationIdList.decorator.d.ts +1 -0
- package/dist/module/zitadel/domain/validation/organizationIdList.decorator.js +19 -0
- package/dist/module/zitadel/index.d.ts +4 -0
- package/dist/module/zitadel/index.js +11 -0
- package/dist/module/zitadel/service/zitadel.provider.d.ts +40 -0
- package/dist/module/zitadel/service/zitadel.provider.js +266 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/service/testIdentityProvider.mock.d.ts +42 -0
- package/dist/testing/service/testIdentityProvider.mock.js +118 -0
- package/dist/testing/service/userStore.contract.d.ts +17 -0
- package/dist/testing/service/userStore.contract.js +156 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +5 -0
- package/package.json +139 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdentityAuthGuard = void 0;
|
|
4
|
+
const identityConnectorExceptionType_enum_1 = require("../../../common/domain/enum/identityConnectorExceptionType.enum");
|
|
5
|
+
const identityConnectorException_1 = require("../domain/exception/identityConnectorException");
|
|
6
|
+
const userStore_1 = require("../domain/model/userStore");
|
|
7
|
+
class IdentityAuthGuard {
|
|
8
|
+
constructor(runtime, store, providers) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
this.runtime = runtime;
|
|
11
|
+
const guard = new.target.name;
|
|
12
|
+
if (!this.runtime?.providers) {
|
|
13
|
+
throw new Error(`${guard} needs the IdentityRuntime: register IdentityConnectorModule.forRoot() at the root of the app.`);
|
|
14
|
+
}
|
|
15
|
+
if (!Array.isArray(providers) || providers.length === 0 || Array.from(providers).some((provider) => !provider)) {
|
|
16
|
+
throw new Error(`${guard} needs at least one identity provider, and every one set: pass the providers it accepts to super().`);
|
|
17
|
+
}
|
|
18
|
+
this.providers = [...providers];
|
|
19
|
+
for (const provider of this.providers) {
|
|
20
|
+
if (!this.runtime.providers.includes(provider)) {
|
|
21
|
+
throw new Error(`${guard} accepts provider "${provider.name}", which is not an instance IdentityConnectorModule.forRoot() ` +
|
|
22
|
+
`registers: list its class in forRoot() only, not in a module's providers as well, and inject it by that class.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
onModuleInit() {
|
|
27
|
+
for (const provider of this.providers) {
|
|
28
|
+
this.assertStoreAllows(provider);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async canActivate(context) {
|
|
32
|
+
const request = context.switchToHttp().getRequest();
|
|
33
|
+
request.user = await this.signIn(request.headers.authorization);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
async signIn(authorization) {
|
|
37
|
+
const accessToken = IdentityAuthGuard.readBearerToken(authorization);
|
|
38
|
+
const provider = this.providerOf(accessToken);
|
|
39
|
+
const identity = { ...(await this.authenticate(provider, accessToken)), provider: provider.name };
|
|
40
|
+
return this.runtime.signInCache.resolve(this.store, identity.provider, identity.id, () => this.resolveUser(provider, accessToken, identity));
|
|
41
|
+
}
|
|
42
|
+
providerOf(accessToken) {
|
|
43
|
+
const provider = this.runtime.providerOfIssuer(IdentityAuthGuard.readUnverifiedIssuer(accessToken) ?? '');
|
|
44
|
+
if (!provider) {
|
|
45
|
+
throw (0, identityConnectorException_1.identityConnectorException)(identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.UNKNOWN_ISSUER);
|
|
46
|
+
}
|
|
47
|
+
if (!this.providers.includes(provider)) {
|
|
48
|
+
throw (0, identityConnectorException_1.identityConnectorException)(identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.PROVIDER_NOT_ACCEPTED);
|
|
49
|
+
}
|
|
50
|
+
return provider;
|
|
51
|
+
}
|
|
52
|
+
static readBearerToken(authorization) {
|
|
53
|
+
const [scheme, accessToken, ...rest] = (authorization ?? '').trim().split(/\s+/);
|
|
54
|
+
if (scheme?.toLowerCase() !== 'bearer' || !accessToken || rest.length > 0) {
|
|
55
|
+
throw (0, identityConnectorException_1.identityConnectorException)(identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.NO_TOKEN);
|
|
56
|
+
}
|
|
57
|
+
return accessToken;
|
|
58
|
+
}
|
|
59
|
+
static readUnverifiedIssuer(accessToken) {
|
|
60
|
+
try {
|
|
61
|
+
const claims = JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64url').toString());
|
|
62
|
+
return typeof claims?.iss === 'string' ? claims.iss : undefined;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
assertStoreAllows(provider) {
|
|
69
|
+
const missing = [
|
|
70
|
+
{ allowed: provider.linkByEmail, option: 'linkByEmail', method: 'linkByEmail' },
|
|
71
|
+
{ allowed: provider.createOnSignIn, option: 'createOnSignIn', method: 'create' },
|
|
72
|
+
].filter(({ allowed, method }) => allowed && this.store[method] === userStore_1.UserStore.prototype[method]);
|
|
73
|
+
if (missing.length > 0) {
|
|
74
|
+
throw new Error(`${this.constructor.name} accepts provider "${provider.name}", which has ` +
|
|
75
|
+
`${missing.map(({ option }) => option).join(' and ')}, but ${this.store.constructor.name} does not ` +
|
|
76
|
+
`implement ${missing.map(({ method }) => `${method}()`).join(' and ')}: every first sign-in it allows would be refused.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async resolveUser(provider, accessToken, identity) {
|
|
80
|
+
const user = await this.store.findByIdentityId(identity.id, identity.provider);
|
|
81
|
+
if (user) {
|
|
82
|
+
return this.store.sync(user, identity);
|
|
83
|
+
}
|
|
84
|
+
return this.signInFirstTime(provider, accessToken, identity);
|
|
85
|
+
}
|
|
86
|
+
async authenticate(provider, accessToken) {
|
|
87
|
+
let identity;
|
|
88
|
+
try {
|
|
89
|
+
identity = await provider.authenticate(accessToken);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if ((0, identityConnectorException_1.identityConnectorExceptionTypeOf)(error) !== undefined) {
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
throw (0, identityConnectorException_1.identityConnectorException)(identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.INVALID_TOKEN);
|
|
96
|
+
}
|
|
97
|
+
if (typeof identity?.id !== 'string' || identity.id.trim() === '') {
|
|
98
|
+
throw (0, identityConnectorException_1.identityConnectorException)(identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.INVALID_TOKEN);
|
|
99
|
+
}
|
|
100
|
+
return identity;
|
|
101
|
+
}
|
|
102
|
+
async signInFirstTime(provider, accessToken, token) {
|
|
103
|
+
const identity = { ...(await provider.readProfile(accessToken, token)), provider: provider.name };
|
|
104
|
+
let user = null;
|
|
105
|
+
if (provider.linkByEmail && identity.email) {
|
|
106
|
+
user = await this.store.linkByEmail(identity);
|
|
107
|
+
}
|
|
108
|
+
if (!user && provider.createOnSignIn) {
|
|
109
|
+
user = await this.store.create(identity);
|
|
110
|
+
}
|
|
111
|
+
if (!user) {
|
|
112
|
+
throw (0, identityConnectorException_1.identityConnectorException)(identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.USER_NOT_LINKED);
|
|
113
|
+
}
|
|
114
|
+
return this.store.sync(user, identity);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.IdentityAuthGuard = IdentityAuthGuard;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { IdentityProvider, ManagementOptionsOf } from '../domain/model/identityProvider';
|
|
2
|
+
import { UserStore } from '../domain/model/userStore';
|
|
3
|
+
import { IdentityRuntime } from './identityRuntime';
|
|
4
|
+
export declare abstract class IdentityManagement<TUser, TProvider extends IdentityProvider = IdentityProvider> {
|
|
5
|
+
private readonly store;
|
|
6
|
+
private readonly provider;
|
|
7
|
+
private readonly runtime;
|
|
8
|
+
private readonly managementOptions;
|
|
9
|
+
constructor(runtime: IdentityRuntime, store: UserStore<TUser>, provider: TProvider, managementOptions: ManagementOptionsOf<TProvider>);
|
|
10
|
+
provision(user: TUser, transaction?: unknown): Promise<void>;
|
|
11
|
+
isFederated(user: TUser): Promise<boolean>;
|
|
12
|
+
createAccountSetupLink(user: TUser, options?: {
|
|
13
|
+
returnUrl?: string;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
private profileOf;
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdentityManagement = void 0;
|
|
4
|
+
const identityConnectorExceptionType_enum_1 = require("../../../common/domain/enum/identityConnectorExceptionType.enum");
|
|
5
|
+
const identityConnectorException_1 = require("../domain/exception/identityConnectorException");
|
|
6
|
+
const validateOptions_1 = require("../domain/validation/validateOptions");
|
|
7
|
+
class IdentityManagement {
|
|
8
|
+
constructor(runtime, store, provider, managementOptions) {
|
|
9
|
+
this.store = store;
|
|
10
|
+
this.provider = provider;
|
|
11
|
+
this.runtime = runtime;
|
|
12
|
+
if (!this.runtime?.providers?.includes(provider)) {
|
|
13
|
+
throw new Error(`${new.target.name} needs a provider registered in IdentityConnectorModule.forRoot(): inject the provider class the module lists.`);
|
|
14
|
+
}
|
|
15
|
+
if (!store) {
|
|
16
|
+
throw new Error(`${new.target.name} needs a user store.`);
|
|
17
|
+
}
|
|
18
|
+
const type = provider.managementOptionsType;
|
|
19
|
+
this.managementOptions = (type
|
|
20
|
+
? (0, validateOptions_1.validateOptions)(type, managementOptions, `${new.target.name}: identity provider "${provider.name}"`, 'management options')
|
|
21
|
+
: managementOptions);
|
|
22
|
+
}
|
|
23
|
+
async provision(user, transaction) {
|
|
24
|
+
const profile = this.profileOf(user);
|
|
25
|
+
if (profile.identityId) {
|
|
26
|
+
this.runtime.signInCache.forget(this.store, this.provider.name, profile.identityId);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const identityId = (await this.provider.findByEmail(profile.email, this.managementOptions)) ??
|
|
30
|
+
(await this.provider.create(profile, this.managementOptions));
|
|
31
|
+
if (!(await this.store.link(user, identityId, this.provider.name, transaction))) {
|
|
32
|
+
const owner = await this.store.findByIdentityId(identityId, this.provider.name, transaction);
|
|
33
|
+
throw (0, identityConnectorException_1.identityConnectorException)(owner
|
|
34
|
+
? identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.IDENTITY_ALREADY_LINKED
|
|
35
|
+
: identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.LINK_REFUSED);
|
|
36
|
+
}
|
|
37
|
+
this.runtime.signInCache.forget(this.store, this.provider.name, identityId);
|
|
38
|
+
}
|
|
39
|
+
async isFederated(user) {
|
|
40
|
+
const { email } = this.profileOf(user);
|
|
41
|
+
return this.provider.isFederated(email, this.managementOptions);
|
|
42
|
+
}
|
|
43
|
+
async createAccountSetupLink(user, options) {
|
|
44
|
+
const { identityId } = this.profileOf(user);
|
|
45
|
+
if (!identityId) {
|
|
46
|
+
throw (0, identityConnectorException_1.identityConnectorException)(identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.USER_NOT_PROVISIONED);
|
|
47
|
+
}
|
|
48
|
+
return this.provider.createAccountSetupLink(identityId, this.managementOptions, options);
|
|
49
|
+
}
|
|
50
|
+
profileOf(user) {
|
|
51
|
+
return this.store.toProfile(user, this.provider.name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.IdentityManagement = IdentityManagement;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ExceptionMapperRegistry } from '@ballistix.digital/exception-mapper';
|
|
2
|
+
import { IdentityProvider } from '../domain/model/identityProvider';
|
|
3
|
+
import { IdentityRuntime } from './identityRuntime';
|
|
4
|
+
import { SignInCache } from './signIn.cache';
|
|
5
|
+
export declare class IdentityRuntimeInternals extends IdentityRuntime {
|
|
6
|
+
readonly providers: IdentityProvider[];
|
|
7
|
+
readonly signInCache: SignInCache;
|
|
8
|
+
private readonly byIssuer;
|
|
9
|
+
constructor(providers: IdentityProvider[], signInCache: SignInCache, exceptionMapperRegistry: ExceptionMapperRegistry);
|
|
10
|
+
providerOfIssuer(issuer: string): IdentityProvider | undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.IdentityRuntimeInternals = void 0;
|
|
16
|
+
const exception_mapper_1 = require("@ballistix.digital/exception-mapper");
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const identityProvidersToken_1 = require("../domain/model/identityProvidersToken");
|
|
19
|
+
const identityRuntime_1 = require("./identityRuntime");
|
|
20
|
+
const signIn_cache_1 = require("./signIn.cache");
|
|
21
|
+
let IdentityRuntimeInternals = class IdentityRuntimeInternals extends identityRuntime_1.IdentityRuntime {
|
|
22
|
+
constructor(providers, signInCache, exceptionMapperRegistry) {
|
|
23
|
+
super();
|
|
24
|
+
this.providers = providers;
|
|
25
|
+
this.signInCache = signInCache;
|
|
26
|
+
this.byIssuer = new Map();
|
|
27
|
+
const names = new Set();
|
|
28
|
+
for (const provider of providers) {
|
|
29
|
+
if (names.has(provider.name)) {
|
|
30
|
+
throw new Error(`Two identity providers share the name "${provider.name}".`);
|
|
31
|
+
}
|
|
32
|
+
names.add(provider.name);
|
|
33
|
+
for (const issuer of provider.issuers) {
|
|
34
|
+
const owner = this.byIssuer.get(issuer);
|
|
35
|
+
if (owner && owner !== provider) {
|
|
36
|
+
throw new Error(`Two identity providers list the issuer "${issuer}".`);
|
|
37
|
+
}
|
|
38
|
+
this.byIssuer.set(issuer, provider);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
providerOfIssuer(issuer) {
|
|
43
|
+
return this.byIssuer.get(issuer);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.IdentityRuntimeInternals = IdentityRuntimeInternals;
|
|
47
|
+
exports.IdentityRuntimeInternals = IdentityRuntimeInternals = __decorate([
|
|
48
|
+
(0, common_1.Injectable)(),
|
|
49
|
+
__param(0, (0, common_1.Inject)(identityProvidersToken_1.IDENTITY_PROVIDERS)),
|
|
50
|
+
__metadata("design:paramtypes", [Array, signIn_cache_1.SignInCache,
|
|
51
|
+
exception_mapper_1.ExceptionMapperRegistry])
|
|
52
|
+
], IdentityRuntimeInternals);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IdentityConnectorExceptionTypeEnum } from '../../../common/domain/enum/identityConnectorExceptionType.enum';
|
|
2
|
+
import { UserStore } from '../domain/model/userStore';
|
|
3
|
+
export type SignInOutcome = {
|
|
4
|
+
user: unknown;
|
|
5
|
+
} | {
|
|
6
|
+
refused: IdentityConnectorExceptionTypeEnum;
|
|
7
|
+
};
|
|
8
|
+
export declare class SignInCache {
|
|
9
|
+
private readonly byStore;
|
|
10
|
+
private readonly running;
|
|
11
|
+
private readonly ttlMs;
|
|
12
|
+
constructor(seconds: number);
|
|
13
|
+
static assertLifetime(seconds: number): void;
|
|
14
|
+
resolve<TUser>(store: UserStore<unknown>, provider: string, identityId: string, signIn: () => Promise<TUser>): Promise<TUser>;
|
|
15
|
+
forget(store: UserStore<unknown>, provider: string, identityId: string): void;
|
|
16
|
+
get(store: UserStore<unknown>, provider: string, identityId: string): SignInOutcome | undefined;
|
|
17
|
+
set(store: UserStore<unknown>, provider: string, identityId: string, outcome: SignInOutcome): void;
|
|
18
|
+
private settle;
|
|
19
|
+
private sweep;
|
|
20
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SignInCache = void 0;
|
|
4
|
+
const identityConnectorExceptionType_enum_1 = require("../../../common/domain/enum/identityConnectorExceptionType.enum");
|
|
5
|
+
const identityConnectorException_1 = require("../domain/exception/identityConnectorException");
|
|
6
|
+
class SignInCache {
|
|
7
|
+
constructor(seconds) {
|
|
8
|
+
this.byStore = new WeakMap();
|
|
9
|
+
this.running = new WeakMap();
|
|
10
|
+
SignInCache.assertLifetime(seconds);
|
|
11
|
+
this.ttlMs = seconds * 1000;
|
|
12
|
+
}
|
|
13
|
+
static assertLifetime(seconds) {
|
|
14
|
+
if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds < 0) {
|
|
15
|
+
throw new Error(`signInCacheSeconds must be a finite number of 0 or more, not ${String(seconds)}.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async resolve(store, provider, identityId, signIn) {
|
|
19
|
+
const cached = this.get(store, provider, identityId);
|
|
20
|
+
if (cached) {
|
|
21
|
+
if ('refused' in cached) {
|
|
22
|
+
throw (0, identityConnectorException_1.identityConnectorException)(cached.refused);
|
|
23
|
+
}
|
|
24
|
+
return cached.user;
|
|
25
|
+
}
|
|
26
|
+
let running = this.running.get(store);
|
|
27
|
+
if (!running) {
|
|
28
|
+
running = new Map();
|
|
29
|
+
this.running.set(store, running);
|
|
30
|
+
}
|
|
31
|
+
const key = JSON.stringify([provider, identityId]);
|
|
32
|
+
const inProgress = running.get(key);
|
|
33
|
+
if (inProgress) {
|
|
34
|
+
return inProgress;
|
|
35
|
+
}
|
|
36
|
+
const attempt = {};
|
|
37
|
+
const isCurrent = () => attempt.outcome === undefined || running.get(key) === attempt.outcome;
|
|
38
|
+
attempt.outcome = this.settle(store, provider, identityId, signIn, isCurrent).finally(() => {
|
|
39
|
+
if (isCurrent()) {
|
|
40
|
+
running.delete(key);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
running.set(key, attempt.outcome);
|
|
44
|
+
return attempt.outcome;
|
|
45
|
+
}
|
|
46
|
+
forget(store, provider, identityId) {
|
|
47
|
+
const key = JSON.stringify([provider, identityId]);
|
|
48
|
+
this.byStore.get(store)?.entries.delete(key);
|
|
49
|
+
this.running.get(store)?.delete(key);
|
|
50
|
+
}
|
|
51
|
+
get(store, provider, identityId) {
|
|
52
|
+
const entries = this.byStore.get(store)?.entries;
|
|
53
|
+
const key = JSON.stringify([provider, identityId]);
|
|
54
|
+
const entry = entries?.get(key);
|
|
55
|
+
if (!entry || entry.expiresAt <= Date.now()) {
|
|
56
|
+
entries?.delete(key);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return entry.outcome;
|
|
60
|
+
}
|
|
61
|
+
set(store, provider, identityId, outcome) {
|
|
62
|
+
if (this.ttlMs <= 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
let partition = this.byStore.get(store);
|
|
66
|
+
if (!partition) {
|
|
67
|
+
partition = { entries: new Map(), nextSweep: 0 };
|
|
68
|
+
this.byStore.set(store, partition);
|
|
69
|
+
}
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
this.sweep(partition, now);
|
|
72
|
+
partition.entries.set(JSON.stringify([provider, identityId]), { outcome, expiresAt: now + this.ttlMs });
|
|
73
|
+
}
|
|
74
|
+
async settle(store, provider, identityId, signIn, isCurrent) {
|
|
75
|
+
try {
|
|
76
|
+
const user = await signIn();
|
|
77
|
+
if (isCurrent()) {
|
|
78
|
+
this.set(store, provider, identityId, { user });
|
|
79
|
+
}
|
|
80
|
+
return user;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (isCurrent() &&
|
|
84
|
+
(0, identityConnectorException_1.identityConnectorExceptionTypeOf)(error) === identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.USER_NOT_LINKED) {
|
|
85
|
+
this.set(store, provider, identityId, { refused: identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.USER_NOT_LINKED });
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
sweep(partition, now) {
|
|
91
|
+
if (now < partition.nextSweep) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
partition.nextSweep = now + this.ttlMs;
|
|
95
|
+
for (const [key, entry] of partition.entries) {
|
|
96
|
+
if (entry.expiresAt <= now) {
|
|
97
|
+
partition.entries.delete(key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.SignInCache = SignInCache;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { JWTPayload } from 'jose';
|
|
2
|
+
export interface SignedTokenVerifierOptions {
|
|
3
|
+
keysUrl: string;
|
|
4
|
+
issuers: [string, ...string[]];
|
|
5
|
+
audience: string | [string, ...string[]];
|
|
6
|
+
}
|
|
7
|
+
export declare class SignedTokenVerifier {
|
|
8
|
+
private readonly options;
|
|
9
|
+
private readonly signingKey;
|
|
10
|
+
constructor(options: SignedTokenVerifierOptions);
|
|
11
|
+
verify(accessToken: string): Promise<JWTPayload>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SignedTokenVerifier = void 0;
|
|
4
|
+
const jose_1 = require("jose");
|
|
5
|
+
const identityConnectorExceptionType_enum_1 = require("../../../common/domain/enum/identityConnectorExceptionType.enum");
|
|
6
|
+
const identityConnectorException_1 = require("../../identity/domain/exception/identityConnectorException");
|
|
7
|
+
const SIGNING_KEYS_TIMEOUT_MS = 5_000;
|
|
8
|
+
const SIGNING_KEYS_MAX_AGE_MS = 10 * 60_000;
|
|
9
|
+
const SIGNING_KEYS_COOLDOWN_MS = 30_000;
|
|
10
|
+
const TOKEN_KEY_ERRORS = new Set([
|
|
11
|
+
jose_1.errors.JWKSNoMatchingKey.code,
|
|
12
|
+
jose_1.errors.JWKSMultipleMatchingKeys.code,
|
|
13
|
+
jose_1.errors.JOSENotSupported.code,
|
|
14
|
+
]);
|
|
15
|
+
class SigningKeysUnavailable extends Error {
|
|
16
|
+
}
|
|
17
|
+
class SignedTokenVerifier {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.options = options;
|
|
20
|
+
const keySet = (0, jose_1.createRemoteJWKSet)(new URL(options.keysUrl), {
|
|
21
|
+
timeoutDuration: SIGNING_KEYS_TIMEOUT_MS,
|
|
22
|
+
cacheMaxAge: SIGNING_KEYS_MAX_AGE_MS,
|
|
23
|
+
cooldownDuration: SIGNING_KEYS_COOLDOWN_MS,
|
|
24
|
+
});
|
|
25
|
+
this.signingKey = async (protectedHeader, token) => {
|
|
26
|
+
if (!protectedHeader.kid) {
|
|
27
|
+
throw new jose_1.errors.JWKSNoMatchingKey();
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return await keySet(protectedHeader, token);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof jose_1.errors.JOSEError && TOKEN_KEY_ERRORS.has(error.code)) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
throw new SigningKeysUnavailable('The signing keys could not be fetched.', { cause: error });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async verify(accessToken) {
|
|
41
|
+
try {
|
|
42
|
+
const { payload } = await (0, jose_1.jwtVerify)(accessToken, this.signingKey, {
|
|
43
|
+
algorithms: ['RS256'],
|
|
44
|
+
issuer: this.options.issuers,
|
|
45
|
+
audience: this.options.audience,
|
|
46
|
+
});
|
|
47
|
+
return payload;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw (0, identityConnectorException_1.identityConnectorException)(error instanceof SigningKeysUnavailable
|
|
51
|
+
? identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.PROVIDER_UNREACHABLE
|
|
52
|
+
: identityConnectorExceptionType_enum_1.IdentityConnectorExceptionTypeEnum.INVALID_TOKEN);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.SignedTokenVerifier = SignedTokenVerifier;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ZitadelManagementOptions = void 0;
|
|
13
|
+
const class_validator_1 = require("class-validator");
|
|
14
|
+
const trimmed_decorator_1 = require("../../../identity/domain/validation/trimmed.decorator");
|
|
15
|
+
class ZitadelManagementOptions {
|
|
16
|
+
}
|
|
17
|
+
exports.ZitadelManagementOptions = ZitadelManagementOptions;
|
|
18
|
+
__decorate([
|
|
19
|
+
(0, trimmed_decorator_1.Trimmed)(),
|
|
20
|
+
(0, class_validator_1.IsString)(),
|
|
21
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
22
|
+
__metadata("design:type", String)
|
|
23
|
+
], ZitadelManagementOptions.prototype, "organizationId", void 0);
|
|
24
|
+
__decorate([
|
|
25
|
+
(0, class_validator_1.ValidateIf)((options) => options.federatedOrganizationIds !== undefined),
|
|
26
|
+
(0, trimmed_decorator_1.TrimmedList)(),
|
|
27
|
+
(0, class_validator_1.IsArray)(),
|
|
28
|
+
(0, class_validator_1.IsString)({ each: true }),
|
|
29
|
+
(0, class_validator_1.IsNotEmpty)({ each: true }),
|
|
30
|
+
__metadata("design:type", Array)
|
|
31
|
+
], ZitadelManagementOptions.prototype, "federatedOrganizationIds", void 0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { IdentityProviderOptions } from '../../../identity/domain/model/identityProviderOptions';
|
|
3
|
+
import { ZitadelServiceUserKey } from './zitadelServiceUserKey';
|
|
4
|
+
export declare class ZitadelProviderOptions extends IdentityProviderOptions {
|
|
5
|
+
issuer: string;
|
|
6
|
+
projectId: string;
|
|
7
|
+
organizationIds: string[];
|
|
8
|
+
roleOrganizationIds: string[];
|
|
9
|
+
serviceUserKey?: ZitadelServiceUserKey;
|
|
10
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ZitadelProviderOptions = void 0;
|
|
13
|
+
require("reflect-metadata");
|
|
14
|
+
const class_transformer_1 = require("class-transformer");
|
|
15
|
+
const class_validator_1 = require("class-validator");
|
|
16
|
+
const identityProviderOptions_1 = require("../../../identity/domain/model/identityProviderOptions");
|
|
17
|
+
const trimmed_decorator_1 = require("../../../identity/domain/validation/trimmed.decorator");
|
|
18
|
+
const organizationIdList_decorator_1 = require("../validation/organizationIdList.decorator");
|
|
19
|
+
const zitadelServiceUserKey_1 = require("./zitadelServiceUserKey");
|
|
20
|
+
class ZitadelProviderOptions extends identityProviderOptions_1.IdentityProviderOptions {
|
|
21
|
+
}
|
|
22
|
+
exports.ZitadelProviderOptions = ZitadelProviderOptions;
|
|
23
|
+
__decorate([
|
|
24
|
+
(0, class_transformer_1.Transform)(({ value }) => (typeof value === 'string' ? value.trim().replace(/\/+$/, '') : value)),
|
|
25
|
+
(0, class_validator_1.IsUrl)({ protocols: ['http', 'https'], require_protocol: true, require_tld: false, allow_underscores: true }, { message: '$property must be an http or https URL' }),
|
|
26
|
+
__metadata("design:type", String)
|
|
27
|
+
], ZitadelProviderOptions.prototype, "issuer", void 0);
|
|
28
|
+
__decorate([
|
|
29
|
+
(0, trimmed_decorator_1.Trimmed)(),
|
|
30
|
+
(0, class_validator_1.IsString)(),
|
|
31
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
32
|
+
__metadata("design:type", String)
|
|
33
|
+
], ZitadelProviderOptions.prototype, "projectId", void 0);
|
|
34
|
+
__decorate([
|
|
35
|
+
(0, organizationIdList_decorator_1.OrganizationIdList)(),
|
|
36
|
+
__metadata("design:type", Array)
|
|
37
|
+
], ZitadelProviderOptions.prototype, "organizationIds", void 0);
|
|
38
|
+
__decorate([
|
|
39
|
+
(0, organizationIdList_decorator_1.OrganizationIdList)(),
|
|
40
|
+
__metadata("design:type", Array)
|
|
41
|
+
], ZitadelProviderOptions.prototype, "roleOrganizationIds", void 0);
|
|
42
|
+
__decorate([
|
|
43
|
+
(0, class_validator_1.ValidateIf)((options) => options.serviceUserKey !== undefined),
|
|
44
|
+
(0, class_validator_1.IsObject)({ message: '$property must be the parsed key file of a service user' }),
|
|
45
|
+
(0, class_validator_1.ValidateNested)({ message: '$property must be the parsed key file of a service user' }),
|
|
46
|
+
(0, class_transformer_1.Type)(() => zitadelServiceUserKey_1.ZitadelServiceUserKey),
|
|
47
|
+
__metadata("design:type", zitadelServiceUserKey_1.ZitadelServiceUserKey)
|
|
48
|
+
], ZitadelProviderOptions.prototype, "serviceUserKey", void 0);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ZitadelServiceUserKey = void 0;
|
|
13
|
+
const class_validator_1 = require("class-validator");
|
|
14
|
+
const trimmed_decorator_1 = require("../../../identity/domain/validation/trimmed.decorator");
|
|
15
|
+
const isRsaPrivateKey_decorator_1 = require("../validation/isRsaPrivateKey.decorator");
|
|
16
|
+
class ZitadelServiceUserKey {
|
|
17
|
+
}
|
|
18
|
+
exports.ZitadelServiceUserKey = ZitadelServiceUserKey;
|
|
19
|
+
__decorate([
|
|
20
|
+
(0, class_validator_1.Equals)('serviceaccount', {
|
|
21
|
+
message: '$property must be "serviceaccount": the key file of a service user, not of an application',
|
|
22
|
+
}),
|
|
23
|
+
__metadata("design:type", String)
|
|
24
|
+
], ZitadelServiceUserKey.prototype, "type", void 0);
|
|
25
|
+
__decorate([
|
|
26
|
+
(0, trimmed_decorator_1.Trimmed)(),
|
|
27
|
+
(0, class_validator_1.IsString)(),
|
|
28
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
29
|
+
__metadata("design:type", String)
|
|
30
|
+
], ZitadelServiceUserKey.prototype, "keyId", void 0);
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, class_validator_1.IsString)(),
|
|
33
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
34
|
+
(0, isRsaPrivateKey_decorator_1.IsRsaPrivateKey)(),
|
|
35
|
+
__metadata("design:type", String)
|
|
36
|
+
], ZitadelServiceUserKey.prototype, "key", void 0);
|
|
37
|
+
__decorate([
|
|
38
|
+
(0, trimmed_decorator_1.Trimmed)(),
|
|
39
|
+
(0, class_validator_1.IsString)(),
|
|
40
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
41
|
+
__metadata("design:type", String)
|
|
42
|
+
], ZitadelServiceUserKey.prototype, "userId", void 0);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IsRsaPrivateKey = IsRsaPrivateKey;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const class_validator_1 = require("class-validator");
|
|
6
|
+
function IsRsaPrivateKey(options) {
|
|
7
|
+
return (target, property) => (0, class_validator_1.registerDecorator)({
|
|
8
|
+
name: 'isRsaPrivateKey',
|
|
9
|
+
target: target.constructor,
|
|
10
|
+
propertyName: property,
|
|
11
|
+
options: { message: '$property must be a PEM-encoded RSA private key Node can read', ...options },
|
|
12
|
+
validator: {
|
|
13
|
+
validate(value) {
|
|
14
|
+
if (typeof value !== 'string') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
return (0, crypto_1.createPrivateKey)(value).asymmetricKeyType === 'rsa';
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function OrganizationIdList(): PropertyDecorator;
|