@owlmeans/server-auth-identity 0.1.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/README.md +94 -0
- package/build/consts.d.ts +7 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +8 -0
- package/build/consts.js.map +1 -0
- package/build/helper.d.ts +3 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +9 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/build/resource.d.ts +5 -0
- package/build/resource.d.ts.map +1 -0
- package/build/resource.js +24 -0
- package/build/resource.js.map +1 -0
- package/build/service.d.ts +3 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +173 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +66 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +45 -0
- package/src/consts.ts +9 -0
- package/src/helper.ts +13 -0
- package/src/index.ts +5 -0
- package/src/resource.ts +33 -0
- package/src/service.ts +195 -0
- package/src/types.ts +75 -0
- package/tsconfig.json +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# @owlmeans/server-auth-identity
|
|
2
|
+
|
|
3
|
+
Mongo-backed local identity resources and provider account linking for OwlMeans server applications.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
- Registers three Mongo resources: local accounts, local profiles, and provider credentials.
|
|
8
|
+
- Provides `IdentityLinkingService` to map external provider profile details into an OwlMeans `AuthPayload`.
|
|
9
|
+
- Keeps external provider identity separate from local product identity and authorization scopes.
|
|
10
|
+
- Designed to work with `@owlmeans/server-auth`, `@owlmeans/server-oidc-rp`, and product-specific module gates.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add @owlmeans/server-auth-identity
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Register identity resources in a server context after Mongo and auth services are available:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { appendAuthIdentityResources } from '@owlmeans/server-auth-identity'
|
|
24
|
+
|
|
25
|
+
appendAuthIdentityResources(context)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Link an external provider profile to a local account/profile:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { AUTH_IDENTITY_LINKING } from '@owlmeans/server-auth-identity'
|
|
32
|
+
import type { IdentityLinkingService } from '@owlmeans/server-auth-identity'
|
|
33
|
+
|
|
34
|
+
const linking = context.service<IdentityLinkingService>(AUTH_IDENTITY_LINKING)
|
|
35
|
+
|
|
36
|
+
const auth = await linking.linkProfile(providerDetails, { username })
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Read identity profiles without mutating records:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { AUTH_IDENTITY_PROFILE } from '@owlmeans/server-auth-identity'
|
|
43
|
+
import type { IdentityProfileResource } from '@owlmeans/server-auth-identity'
|
|
44
|
+
|
|
45
|
+
const profiles = context.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
|
46
|
+
const profile = await profiles.load(profileId, 'profileId')
|
|
47
|
+
|
|
48
|
+
const { items } = await profiles.list({ entityId, profileId } as any)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
### Resource Factories
|
|
54
|
+
|
|
55
|
+
- `makeIdentityAccountResource(dbAlias?)` - local account record, one per user.
|
|
56
|
+
- `makeIdentityProfileResource(dbAlias?)` - local profile record with `entityId`, `role`, `scopes`, and optional expiry.
|
|
57
|
+
- `makeIdentityCredentialsResource(dbAlias?)` - provider credentials link record.
|
|
58
|
+
- `appendAuthIdentityResources(context, dbAlias?)` - registers all resources and `IdentityLinkingService`.
|
|
59
|
+
|
|
60
|
+
### Aliases
|
|
61
|
+
|
|
62
|
+
- `AUTH_IDENTITY_ACCOUNT` - account resource alias.
|
|
63
|
+
- `AUTH_IDENTITY_PROFILE` - profile resource alias.
|
|
64
|
+
- `AUTH_IDENTITY_CREDENTIALS` - provider credentials resource alias.
|
|
65
|
+
- `AUTH_IDENTITY_LINKING` - linking service alias.
|
|
66
|
+
|
|
67
|
+
### Types
|
|
68
|
+
|
|
69
|
+
- `IdentityAccount` - `Profile & ResourceRecord`; `credential` is the generated local entity slug.
|
|
70
|
+
- `IdentityProfile` - `Profile & ResourceRecord`; includes `profileId`, `userId?`, `role`, `entityId`, `scopes`, `expiresAt?`.
|
|
71
|
+
- `IdentityCredentials` - `AuthCredentials & ResourceRecord`; includes `profileId` and derived provider keys.
|
|
72
|
+
- `IdentityLinkingService` - `getLinkedProfile`, `linkProfile`, `linkCredentials`, `getOwnerProfiles`, `getOwnerCredentials`.
|
|
73
|
+
|
|
74
|
+
## Key Derivation
|
|
75
|
+
|
|
76
|
+
- Account `credential`: generated Base58 local entity slug.
|
|
77
|
+
- Profile `profileId`: `"{type}:{accountId}"`.
|
|
78
|
+
- Credentials `userId`: `"{type}:{service}:{providerSub}"`.
|
|
79
|
+
- Credentials `credential`: `"service:{type}:{service}"`.
|
|
80
|
+
|
|
81
|
+
## Product-Viable Integration Notes
|
|
82
|
+
|
|
83
|
+
- Google/OIDC login is only the provider bootstrap path; durable authorization data lives in `IdentityProfile`.
|
|
84
|
+
- Backend contexts should register `appendAuthService(context)`, then `appendAuthIdentityResources(context)`, then product gate services.
|
|
85
|
+
- Product gates should read `AUTH_IDENTITY_PROFILE`, verify `entityId`, reject expired or blocked profiles, and compare gate params against profile scopes.
|
|
86
|
+
- `Resource.pick()` is destructive and deletes the matching record. Never use `pick()` for gate or handler reads; use `load()` or `list()`.
|
|
87
|
+
|
|
88
|
+
## Related Packages
|
|
89
|
+
|
|
90
|
+
- [`@owlmeans/auth`](../auth) - core `AuthPayload`, `AuthRole`, `Profile`, and auth errors.
|
|
91
|
+
- [`@owlmeans/server-auth`](../server-auth) - bearer verification and default auth guard.
|
|
92
|
+
- [`@owlmeans/server-oidc-rp`](../server-oidc-rp) - provider exchange and account-linking interface compatibility.
|
|
93
|
+
- [`@owlmeans/oidc`](../oidc) - `ProviderProfileDetails` and provider config types.
|
|
94
|
+
- [`@owlmeans/mongo-resource`](../mongo-resource) - Mongo-backed resource implementation.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const AUTH_IDENTITY_ACCOUNT = "auth-identity:account";
|
|
2
|
+
export declare const AUTH_IDENTITY_PROFILE = "auth-identity:profile";
|
|
3
|
+
export declare const AUTH_IDENTITY_CREDENTIALS = "auth-identity:credentials";
|
|
4
|
+
export declare const AUTH_IDENTITY_LINKING = "auth-identity:linking";
|
|
5
|
+
export declare const LOGIN_SERVICE_PREFIX = "service";
|
|
6
|
+
export declare const EXTERNAL_KEY_DELIMITER = ":";
|
|
7
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAC5D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,qBAAqB,0BAA0B,CAAA;AAG5D,eAAO,MAAM,oBAAoB,YAAY,CAAA;AAC7C,eAAO,MAAM,sBAAsB,MAAM,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const AUTH_IDENTITY_ACCOUNT = 'auth-identity:account';
|
|
2
|
+
export const AUTH_IDENTITY_PROFILE = 'auth-identity:profile';
|
|
3
|
+
export const AUTH_IDENTITY_CREDENTIALS = 'auth-identity:credentials';
|
|
4
|
+
export const AUTH_IDENTITY_LINKING = 'auth-identity:linking';
|
|
5
|
+
// Helper prefixes for stable key derivation
|
|
6
|
+
export const LOGIN_SERVICE_PREFIX = 'service';
|
|
7
|
+
export const EXTERNAL_KEY_DELIMITER = ':';
|
|
8
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAA;AAE5D,4CAA4C;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAA;AAC7C,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIrD,eAAO,MAAM,2BAA2B,GACtC,SAAS,YAAY,CAAC,GAAG,CAAC,EAC1B,UAAU,MAAM,KACf,IAKF,CAAA"}
|
package/build/helper.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource } from './resource.js';
|
|
2
|
+
import { makeIdentityLinkingService } from './service.js';
|
|
3
|
+
export const appendAuthIdentityResources = (context, dbAlias) => {
|
|
4
|
+
context.registerResource(makeIdentityAccountResource(dbAlias));
|
|
5
|
+
context.registerResource(makeIdentityProfileResource(dbAlias));
|
|
6
|
+
context.registerResource(makeIdentityCredentialsResource(dbAlias));
|
|
7
|
+
context.registerService(makeIdentityLinkingService());
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,+BAA+B,EAAE,MAAM,eAAe,CAAA;AACzH,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAEzD,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,OAA0B,EAC1B,OAAgB,EACV,EAAE;IACR,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9D,OAAO,CAAC,gBAAgB,CAAC,+BAA+B,CAAC,OAAO,CAAC,CAAC,CAAA;IAClE,OAAO,CAAC,eAAe,CAAC,0BAA0B,EAAE,CAAC,CAAA;AACvD,CAAC,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAE3B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IdentityAccountResource, IdentityProfileResource, IdentityCredentialsResource } from './types.js';
|
|
2
|
+
export declare const makeIdentityAccountResource: (dbAlias?: string) => IdentityAccountResource;
|
|
3
|
+
export declare const makeIdentityProfileResource: (dbAlias?: string) => IdentityProfileResource;
|
|
4
|
+
export declare const makeIdentityCredentialsResource: (dbAlias?: string) => IdentityCredentialsResource;
|
|
5
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,uBAAuB,EAAmB,uBAAuB,EAAuB,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAGtK,eAAO,MAAM,2BAA2B,GAAI,UAAU,MAAM,KAAG,uBAQ9D,CAAA;AAED,eAAO,MAAM,2BAA2B,GAAI,UAAU,MAAM,KAAG,uBAS9D,CAAA;AAED,eAAO,MAAM,+BAA+B,GAAI,UAAU,MAAM,KAAG,2BAOlE,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { makeMongoResource } from '@owlmeans/mongo-resource';
|
|
2
|
+
import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS } from './consts.js';
|
|
3
|
+
export const makeIdentityAccountResource = (dbAlias) => {
|
|
4
|
+
const resource = makeMongoResource(AUTH_IDENTITY_ACCOUNT, dbAlias);
|
|
5
|
+
resource.index('credential', { credential: 1 }, { unique: true });
|
|
6
|
+
resource.index('entityId', { entityId: 1 });
|
|
7
|
+
resource.index('secret', { secret: 1 }, { unique: true, sparse: true });
|
|
8
|
+
return resource;
|
|
9
|
+
};
|
|
10
|
+
export const makeIdentityProfileResource = (dbAlias) => {
|
|
11
|
+
const resource = makeMongoResource(AUTH_IDENTITY_PROFILE, dbAlias);
|
|
12
|
+
resource.index('userId', { userId: 1 });
|
|
13
|
+
resource.index('entityId', { entityId: 1 });
|
|
14
|
+
resource.index('role', { role: 1, entityId: 1 });
|
|
15
|
+
resource.index('profile', { profileId: 1, entityId: 1 }, { unique: true });
|
|
16
|
+
return resource;
|
|
17
|
+
};
|
|
18
|
+
export const makeIdentityCredentialsResource = (dbAlias) => {
|
|
19
|
+
const resource = makeMongoResource(AUTH_IDENTITY_CREDENTIALS, dbAlias);
|
|
20
|
+
resource.index('provider', { type: 1, userId: 1, credential: 1 }, { unique: true });
|
|
21
|
+
resource.index('profileId', { profileId: 1 });
|
|
22
|
+
return resource;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAErG,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,OAAgB,EAA2B,EAAE;IACvF,MAAM,QAAQ,GAAG,iBAAiB,CAChC,qBAAqB,EAAE,OAAO,CAC/B,CAAA;IACD,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACjE,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3C,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACvE,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,OAAgB,EAA2B,EAAE;IACvF,MAAM,QAAQ,GAAG,iBAAiB,CAChC,qBAAqB,EAAE,OAAO,CAC/B,CAAA;IACD,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;IACvC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3C,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IAChD,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1E,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,OAAgB,EAA+B,EAAE;IAC/F,MAAM,QAAQ,GAAG,iBAAiB,CAChC,yBAAyB,EAAE,OAAO,CACnC,CAAA;IACD,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;IACnF,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAA;IAC7C,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA8F,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAepJ,eAAO,MAAM,0BAA0B,QAAO,sBA6K7C,CAAA"}
|
package/build/service.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { appendContextual } from '@owlmeans/context';
|
|
2
|
+
import { ALL_SCOPES, AuthRole } from '@owlmeans/auth';
|
|
3
|
+
import { createIdOfLength, IdStyle } from '@owlmeans/basic-ids';
|
|
4
|
+
import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js';
|
|
5
|
+
const MAX_SLUG_RETRIES = 5;
|
|
6
|
+
// Stable external login key: "{type}:{service}:{providerSub}"
|
|
7
|
+
const externalKey = (details) => [details.type, details.service, details.userId].join(EXTERNAL_KEY_DELIMITER);
|
|
8
|
+
// Login-service credential stored on profile and credentials: "service:{type}:{service}"
|
|
9
|
+
const loginService = (details) => [LOGIN_SERVICE_PREFIX, details.type, details.service].join(EXTERNAL_KEY_DELIMITER);
|
|
10
|
+
export const makeIdentityLinkingService = () => {
|
|
11
|
+
const service = appendContextual(AUTH_IDENTITY_LINKING, {
|
|
12
|
+
getLinkedProfile: async (details) => {
|
|
13
|
+
const ctx = service.ctx;
|
|
14
|
+
const credsResource = ctx.resource(AUTH_IDENTITY_CREDENTIALS);
|
|
15
|
+
const { items: creds } = await credsResource.list({
|
|
16
|
+
criteria: {
|
|
17
|
+
type: details.type,
|
|
18
|
+
userId: externalKey(details),
|
|
19
|
+
credential: loginService(details),
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const cred = creds[0] ?? null;
|
|
23
|
+
if (cred == null)
|
|
24
|
+
return null;
|
|
25
|
+
const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
|
|
26
|
+
const profile = await profileResource.load(cred.profileId, 'profileId');
|
|
27
|
+
if (profile == null)
|
|
28
|
+
return null;
|
|
29
|
+
return {
|
|
30
|
+
type: details.type,
|
|
31
|
+
role: profile.role,
|
|
32
|
+
userId: profile.userId ?? profile.profileId,
|
|
33
|
+
profileId: profile.profileId,
|
|
34
|
+
entityId: profile.entityId,
|
|
35
|
+
scopes: profile.scopes,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
linkProfile: async (details, meta) => {
|
|
39
|
+
const ctx = service.ctx;
|
|
40
|
+
const accountResource = ctx.resource(AUTH_IDENTITY_ACCOUNT);
|
|
41
|
+
const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
|
|
42
|
+
const credsResource = ctx.resource(AUTH_IDENTITY_CREDENTIALS);
|
|
43
|
+
// Generate a unique local entity slug stored as the account credential.
|
|
44
|
+
let accountId = null;
|
|
45
|
+
let entityId = null;
|
|
46
|
+
for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
|
|
47
|
+
const credential = createIdOfLength(16, IdStyle.Base58);
|
|
48
|
+
try {
|
|
49
|
+
const account = await accountResource.create({
|
|
50
|
+
credential,
|
|
51
|
+
name: meta.username,
|
|
52
|
+
entityId: credential,
|
|
53
|
+
});
|
|
54
|
+
accountId = account.id;
|
|
55
|
+
entityId = account.credential;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err?.code === 11000 || err?.message?.includes('duplicate'))
|
|
60
|
+
continue;
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (accountId == null || entityId == null) {
|
|
65
|
+
throw new Error('Failed to generate unique account credential after retries');
|
|
66
|
+
}
|
|
67
|
+
const profileId = [details.type, accountId].join(EXTERNAL_KEY_DELIMITER);
|
|
68
|
+
const svc = loginService(details);
|
|
69
|
+
let profile;
|
|
70
|
+
try {
|
|
71
|
+
profile = await profileResource.create({
|
|
72
|
+
profileId,
|
|
73
|
+
userId: accountId,
|
|
74
|
+
credential: svc,
|
|
75
|
+
role: AuthRole.User,
|
|
76
|
+
name: meta.username,
|
|
77
|
+
entityId,
|
|
78
|
+
scopes: [ALL_SCOPES],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err?.code === 11000 || err?.message?.includes('duplicate')) {
|
|
83
|
+
profile = await profileResource.get(profileId, 'profileId');
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
await credsResource.create({
|
|
90
|
+
challenge: '',
|
|
91
|
+
type: details.type,
|
|
92
|
+
userId: externalKey(details),
|
|
93
|
+
profileId,
|
|
94
|
+
credential: svc,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
type: details.type,
|
|
98
|
+
role: profile.role,
|
|
99
|
+
userId: profile.userId ?? profile.profileId,
|
|
100
|
+
profileId: profile.profileId,
|
|
101
|
+
entityId: profile.entityId,
|
|
102
|
+
scopes: profile.scopes,
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
linkCredentials: async (details) => {
|
|
106
|
+
const result = await service.getLinkedProfile(details);
|
|
107
|
+
if (result == null) {
|
|
108
|
+
throw new Error('Cannot link credentials: profile not found');
|
|
109
|
+
}
|
|
110
|
+
if (details.profileId != null) {
|
|
111
|
+
const ctx = service.ctx;
|
|
112
|
+
const credsResource = ctx.resource(AUTH_IDENTITY_CREDENTIALS);
|
|
113
|
+
await credsResource.create({
|
|
114
|
+
challenge: '',
|
|
115
|
+
type: details.type,
|
|
116
|
+
userId: externalKey(details),
|
|
117
|
+
profileId: details.profileId,
|
|
118
|
+
credential: loginService(details),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
},
|
|
123
|
+
getOwnerProfiles: async (entityId) => {
|
|
124
|
+
const ctx = service.ctx;
|
|
125
|
+
const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
|
|
126
|
+
const { items: profiles } = await profileResource.list({ criteria: { entityId } });
|
|
127
|
+
return profiles.map(p => ({
|
|
128
|
+
id: p.profileId,
|
|
129
|
+
name: p.name,
|
|
130
|
+
credential: p.credential,
|
|
131
|
+
entityId: p.entityId,
|
|
132
|
+
scopes: p.scopes,
|
|
133
|
+
groups: p.groups,
|
|
134
|
+
permissions: p.permissions,
|
|
135
|
+
attributes: p.attributes,
|
|
136
|
+
}));
|
|
137
|
+
},
|
|
138
|
+
getOwnerCredentials: async (userId, entityId, type) => {
|
|
139
|
+
const ctx = service.ctx;
|
|
140
|
+
const profileResource = ctx.resource(AUTH_IDENTITY_PROFILE);
|
|
141
|
+
const credsResource = ctx.resource(AUTH_IDENTITY_CREDENTIALS);
|
|
142
|
+
const profileFilter = { userId };
|
|
143
|
+
if (entityId != null)
|
|
144
|
+
profileFilter.entityId = entityId;
|
|
145
|
+
const { items: profiles } = await profileResource.list(profileFilter);
|
|
146
|
+
if (profiles.length === 0)
|
|
147
|
+
return undefined;
|
|
148
|
+
const credsFilter = {
|
|
149
|
+
profileId: { $in: profiles.map(p => p.profileId) }
|
|
150
|
+
};
|
|
151
|
+
if (type != null)
|
|
152
|
+
credsFilter.type = type;
|
|
153
|
+
const { items: creds } = await credsResource.list(credsFilter);
|
|
154
|
+
if (creds.length === 0)
|
|
155
|
+
return undefined;
|
|
156
|
+
const cred = creds[0];
|
|
157
|
+
const profile = profiles.find(p => p.profileId === cred.profileId) ?? profiles[0];
|
|
158
|
+
return {
|
|
159
|
+
type: cred.type,
|
|
160
|
+
role: profile.role,
|
|
161
|
+
userId: profile.userId ?? profile.profileId,
|
|
162
|
+
profileId: profile.profileId,
|
|
163
|
+
entityId: profile.entityId,
|
|
164
|
+
scopes: profile.scopes,
|
|
165
|
+
challenge: cred.challenge,
|
|
166
|
+
credential: cred.credential,
|
|
167
|
+
publicKey: cred.publicKey,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
return service;
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ResourceRecord } from '@owlmeans/resource';
|
|
2
|
+
import type { MongoResource } from '@owlmeans/mongo-resource';
|
|
3
|
+
import type { InitializedService } from '@owlmeans/context';
|
|
4
|
+
import type { ServerConfig, ServerContext } from '@owlmeans/server-context';
|
|
5
|
+
import type { AuthCredentials, AuthPayload, AuthRole, Profile } from '@owlmeans/auth';
|
|
6
|
+
import type { ProviderProfileDetails } from '@owlmeans/oidc';
|
|
7
|
+
/**
|
|
8
|
+
* Local account record — one per user.
|
|
9
|
+
* id is the Mongo resource id; credential is the stable unique local entity slug.
|
|
10
|
+
*/
|
|
11
|
+
export interface IdentityAccount extends Profile, ResourceRecord {
|
|
12
|
+
id: string;
|
|
13
|
+
credential: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Local profile record — ties a user to an entity with a role.
|
|
17
|
+
* Mirrors IAMProfile from @owlmeans/auth backend.
|
|
18
|
+
*/
|
|
19
|
+
export interface IdentityProfile extends Profile, ResourceRecord {
|
|
20
|
+
id: string;
|
|
21
|
+
profileId: string;
|
|
22
|
+
userId?: string;
|
|
23
|
+
role: AuthRole;
|
|
24
|
+
expiresAt?: Date;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Provider credentials record — one per provider link per profile.
|
|
28
|
+
* Maps an external provider subject to a local profile using auth-native fields.
|
|
29
|
+
* credential stores the login-service key: "service:{type}:{service}"
|
|
30
|
+
* userId stores the derived external login key: "{type}:{service}:{providerSub}"
|
|
31
|
+
*/
|
|
32
|
+
export interface IdentityCredentials extends AuthCredentials, ResourceRecord {
|
|
33
|
+
profileId: string;
|
|
34
|
+
}
|
|
35
|
+
export type IdentityAccountResource = MongoResource<IdentityAccount>;
|
|
36
|
+
export type IdentityProfileResource = MongoResource<IdentityProfile>;
|
|
37
|
+
export type IdentityCredentialsResource = MongoResource<IdentityCredentials>;
|
|
38
|
+
/**
|
|
39
|
+
* Compatible with AccountLinkingService from @owlmeans/server-oidc-rp
|
|
40
|
+
* but defined independently to avoid circular dependency.
|
|
41
|
+
*/
|
|
42
|
+
export interface IdentityLinkingService extends InitializedService {
|
|
43
|
+
getLinkedProfile: (details: ProviderProfileDetails) => Promise<AuthPayload | null>;
|
|
44
|
+
linkProfile: (details: ProviderProfileDetails, meta: AccountMeta) => Promise<AuthPayload>;
|
|
45
|
+
linkCredentials: (details: ProviderProfileDetails) => Promise<AuthPayload>;
|
|
46
|
+
getOwnerProfiles: (entityId: string) => Promise<Profile[]>;
|
|
47
|
+
getOwnerCredentials: (userId: string, entityId?: string, type?: string) => Promise<AuthCredentials | undefined>;
|
|
48
|
+
}
|
|
49
|
+
export interface AccountMeta {
|
|
50
|
+
username: string;
|
|
51
|
+
force?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface IdentityConfig extends ServerConfig {
|
|
54
|
+
}
|
|
55
|
+
export interface IdentityContext<C extends IdentityConfig = IdentityConfig> extends ServerContext<C> {
|
|
56
|
+
}
|
|
57
|
+
export interface GoogleUserInfo {
|
|
58
|
+
sub: string;
|
|
59
|
+
email: string;
|
|
60
|
+
email_verified?: boolean;
|
|
61
|
+
name?: string;
|
|
62
|
+
given_name?: string;
|
|
63
|
+
family_name?: string;
|
|
64
|
+
picture?: string;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAE5D;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,OAAO,EAAE,cAAc;IAC9D,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,OAAO,EAAE,cAAc;IAC9D,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe,EAAE,cAAc;IAC1E,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;AACpE,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;AACpE,MAAM,MAAM,2BAA2B,GAAG,aAAa,CAAC,mBAAmB,CAAC,CAAA;AAE5E;;;GAGG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,gBAAgB,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAClF,WAAW,EAAE,CAAC,OAAO,EAAE,sBAAsB,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IACzF,eAAe,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1E,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAC1D,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAA;CAChH;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY;CACnD;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CACnG;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/server-auth-identity",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -b",
|
|
8
|
+
"dev": "sleep 24 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
9
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty",
|
|
10
|
+
"test": "bun test ./tests"
|
|
11
|
+
},
|
|
12
|
+
"main": "build/index.js",
|
|
13
|
+
"module": "build/index.js",
|
|
14
|
+
"types": "build/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./build/index.js",
|
|
18
|
+
"require": "./build/index.js",
|
|
19
|
+
"default": "./build/index.js",
|
|
20
|
+
"module": "./build/index.js",
|
|
21
|
+
"types": "./build/index.d.ts"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@owlmeans/auth": "^0.1.3",
|
|
26
|
+
"@owlmeans/basic-ids": "^0.1.3",
|
|
27
|
+
"@owlmeans/context": "^0.1.3",
|
|
28
|
+
"@owlmeans/mongo-resource": "^0.1.3",
|
|
29
|
+
"@owlmeans/oidc": "^0.1.3",
|
|
30
|
+
"@owlmeans/resource": "^0.1.3",
|
|
31
|
+
"@owlmeans/server-context": "^0.1.3"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"mongodb": "*"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@owlmeans/dep-config": "workspace:*",
|
|
38
|
+
"@types/bun": "^1.3.0",
|
|
39
|
+
"nodemon": "^3.1.11",
|
|
40
|
+
"typescript": "^6.0.2"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/consts.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
export const AUTH_IDENTITY_ACCOUNT = 'auth-identity:account'
|
|
3
|
+
export const AUTH_IDENTITY_PROFILE = 'auth-identity:profile'
|
|
4
|
+
export const AUTH_IDENTITY_CREDENTIALS = 'auth-identity:credentials'
|
|
5
|
+
export const AUTH_IDENTITY_LINKING = 'auth-identity:linking'
|
|
6
|
+
|
|
7
|
+
// Helper prefixes for stable key derivation
|
|
8
|
+
export const LOGIN_SERVICE_PREFIX = 'service'
|
|
9
|
+
export const EXTERNAL_KEY_DELIMITER = ':'
|
package/src/helper.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
2
|
+
import { makeIdentityAccountResource, makeIdentityProfileResource, makeIdentityCredentialsResource } from './resource.js'
|
|
3
|
+
import { makeIdentityLinkingService } from './service.js'
|
|
4
|
+
|
|
5
|
+
export const appendAuthIdentityResources = (
|
|
6
|
+
context: BasicContext<any>,
|
|
7
|
+
dbAlias?: string
|
|
8
|
+
): void => {
|
|
9
|
+
context.registerResource(makeIdentityAccountResource(dbAlias))
|
|
10
|
+
context.registerResource(makeIdentityProfileResource(dbAlias))
|
|
11
|
+
context.registerResource(makeIdentityCredentialsResource(dbAlias))
|
|
12
|
+
context.registerService(makeIdentityLinkingService())
|
|
13
|
+
}
|
package/src/index.ts
ADDED
package/src/resource.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { makeMongoResource } from '@owlmeans/mongo-resource'
|
|
2
|
+
import type { IdentityAccount, IdentityAccountResource, IdentityProfile, IdentityProfileResource, IdentityCredentials, IdentityCredentialsResource } from './types.js'
|
|
3
|
+
import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS } from './consts.js'
|
|
4
|
+
|
|
5
|
+
export const makeIdentityAccountResource = (dbAlias?: string): IdentityAccountResource => {
|
|
6
|
+
const resource = makeMongoResource<IdentityAccount, IdentityAccountResource>(
|
|
7
|
+
AUTH_IDENTITY_ACCOUNT, dbAlias
|
|
8
|
+
)
|
|
9
|
+
resource.index('credential', { credential: 1 }, { unique: true })
|
|
10
|
+
resource.index('entityId', { entityId: 1 })
|
|
11
|
+
resource.index('secret', { secret: 1 }, { unique: true, sparse: true })
|
|
12
|
+
return resource
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const makeIdentityProfileResource = (dbAlias?: string): IdentityProfileResource => {
|
|
16
|
+
const resource = makeMongoResource<IdentityProfile, IdentityProfileResource>(
|
|
17
|
+
AUTH_IDENTITY_PROFILE, dbAlias
|
|
18
|
+
)
|
|
19
|
+
resource.index('userId', { userId: 1 })
|
|
20
|
+
resource.index('entityId', { entityId: 1 })
|
|
21
|
+
resource.index('role', { role: 1, entityId: 1 })
|
|
22
|
+
resource.index('profile', { profileId: 1, entityId: 1 }, { unique: true })
|
|
23
|
+
return resource
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const makeIdentityCredentialsResource = (dbAlias?: string): IdentityCredentialsResource => {
|
|
27
|
+
const resource = makeMongoResource<IdentityCredentials, IdentityCredentialsResource>(
|
|
28
|
+
AUTH_IDENTITY_CREDENTIALS, dbAlias
|
|
29
|
+
)
|
|
30
|
+
resource.index('provider', { type: 1, userId: 1, credential: 1 }, { unique: true })
|
|
31
|
+
resource.index('profileId', { profileId: 1 })
|
|
32
|
+
return resource
|
|
33
|
+
}
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { appendContextual } from '@owlmeans/context'
|
|
2
|
+
import type { ServerConfig, ServerContext } from '@owlmeans/server-context'
|
|
3
|
+
import type { AuthCredentials, AuthPayload, Profile } from '@owlmeans/auth'
|
|
4
|
+
import { ALL_SCOPES, AuthRole } from '@owlmeans/auth'
|
|
5
|
+
import type { ProviderProfileDetails } from '@owlmeans/oidc'
|
|
6
|
+
import { createIdOfLength, IdStyle } from '@owlmeans/basic-ids'
|
|
7
|
+
import type { AccountMeta, IdentityAccountResource, IdentityProfileResource, IdentityCredentialsResource, IdentityLinkingService } from './types.js'
|
|
8
|
+
import { AUTH_IDENTITY_ACCOUNT, AUTH_IDENTITY_PROFILE, AUTH_IDENTITY_CREDENTIALS, AUTH_IDENTITY_LINKING, LOGIN_SERVICE_PREFIX, EXTERNAL_KEY_DELIMITER } from './consts.js'
|
|
9
|
+
|
|
10
|
+
type Context = ServerContext<ServerConfig>
|
|
11
|
+
|
|
12
|
+
const MAX_SLUG_RETRIES = 5
|
|
13
|
+
|
|
14
|
+
// Stable external login key: "{type}:{service}:{providerSub}"
|
|
15
|
+
const externalKey = (details: ProviderProfileDetails): string =>
|
|
16
|
+
[details.type, details.service, details.userId].join(EXTERNAL_KEY_DELIMITER)
|
|
17
|
+
|
|
18
|
+
// Login-service credential stored on profile and credentials: "service:{type}:{service}"
|
|
19
|
+
const loginService = (details: ProviderProfileDetails): string =>
|
|
20
|
+
[LOGIN_SERVICE_PREFIX, details.type, details.service].join(EXTERNAL_KEY_DELIMITER)
|
|
21
|
+
|
|
22
|
+
export const makeIdentityLinkingService = (): IdentityLinkingService => {
|
|
23
|
+
const service: IdentityLinkingService = appendContextual<IdentityLinkingService>(AUTH_IDENTITY_LINKING, {
|
|
24
|
+
getLinkedProfile: async (details: ProviderProfileDetails): Promise<AuthPayload | null> => {
|
|
25
|
+
const ctx = service.ctx as Context
|
|
26
|
+
const credsResource = ctx.resource<IdentityCredentialsResource>(AUTH_IDENTITY_CREDENTIALS)
|
|
27
|
+
|
|
28
|
+
const { items: creds } = await credsResource.list({
|
|
29
|
+
criteria: {
|
|
30
|
+
type: details.type,
|
|
31
|
+
userId: externalKey(details),
|
|
32
|
+
credential: loginService(details),
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
const cred = creds[0] ?? null
|
|
36
|
+
if (cred == null) return null
|
|
37
|
+
|
|
38
|
+
const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
|
39
|
+
const profile = await profileResource.load(cred.profileId, 'profileId')
|
|
40
|
+
if (profile == null) return null
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
type: details.type,
|
|
44
|
+
role: profile.role,
|
|
45
|
+
userId: profile.userId ?? profile.profileId,
|
|
46
|
+
profileId: profile.profileId,
|
|
47
|
+
entityId: profile.entityId,
|
|
48
|
+
scopes: profile.scopes,
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
linkProfile: async (details: ProviderProfileDetails, meta: AccountMeta): Promise<AuthPayload> => {
|
|
53
|
+
const ctx = service.ctx as Context
|
|
54
|
+
const accountResource = ctx.resource<IdentityAccountResource>(AUTH_IDENTITY_ACCOUNT)
|
|
55
|
+
const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
|
56
|
+
const credsResource = ctx.resource<IdentityCredentialsResource>(AUTH_IDENTITY_CREDENTIALS)
|
|
57
|
+
|
|
58
|
+
// Generate a unique local entity slug stored as the account credential.
|
|
59
|
+
let accountId: string | null = null
|
|
60
|
+
let entityId: string | null = null
|
|
61
|
+
for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
|
|
62
|
+
const credential = createIdOfLength(16, IdStyle.Base58)
|
|
63
|
+
try {
|
|
64
|
+
const account = await accountResource.create({
|
|
65
|
+
credential,
|
|
66
|
+
name: meta.username,
|
|
67
|
+
entityId: credential,
|
|
68
|
+
})
|
|
69
|
+
accountId = account.id
|
|
70
|
+
entityId = account.credential
|
|
71
|
+
break
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
if (err?.code === 11000 || err?.message?.includes('duplicate')) continue
|
|
74
|
+
throw err
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (accountId == null || entityId == null) {
|
|
79
|
+
throw new Error('Failed to generate unique account credential after retries')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const profileId = [details.type, accountId].join(EXTERNAL_KEY_DELIMITER)
|
|
83
|
+
const svc = loginService(details)
|
|
84
|
+
|
|
85
|
+
let profile
|
|
86
|
+
try {
|
|
87
|
+
profile = await profileResource.create({
|
|
88
|
+
profileId,
|
|
89
|
+
userId: accountId,
|
|
90
|
+
credential: svc,
|
|
91
|
+
role: AuthRole.User,
|
|
92
|
+
name: meta.username,
|
|
93
|
+
entityId,
|
|
94
|
+
scopes: [ALL_SCOPES],
|
|
95
|
+
})
|
|
96
|
+
} catch (err: any) {
|
|
97
|
+
if (err?.code === 11000 || err?.message?.includes('duplicate')) {
|
|
98
|
+
profile = await profileResource.get(profileId, 'profileId')
|
|
99
|
+
} else {
|
|
100
|
+
throw err
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await credsResource.create({
|
|
105
|
+
challenge: '',
|
|
106
|
+
type: details.type,
|
|
107
|
+
userId: externalKey(details),
|
|
108
|
+
profileId,
|
|
109
|
+
credential: svc,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
type: details.type,
|
|
114
|
+
role: profile.role,
|
|
115
|
+
userId: profile.userId ?? profile.profileId,
|
|
116
|
+
profileId: profile.profileId,
|
|
117
|
+
entityId: profile.entityId,
|
|
118
|
+
scopes: profile.scopes,
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
linkCredentials: async (details: ProviderProfileDetails): Promise<AuthPayload> => {
|
|
123
|
+
const result = await service.getLinkedProfile(details)
|
|
124
|
+
if (result == null) {
|
|
125
|
+
throw new Error('Cannot link credentials: profile not found')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (details.profileId != null) {
|
|
129
|
+
const ctx = service.ctx as Context
|
|
130
|
+
const credsResource = ctx.resource<IdentityCredentialsResource>(AUTH_IDENTITY_CREDENTIALS)
|
|
131
|
+
await credsResource.create({
|
|
132
|
+
challenge: '',
|
|
133
|
+
type: details.type,
|
|
134
|
+
userId: externalKey(details),
|
|
135
|
+
profileId: details.profileId,
|
|
136
|
+
credential: loginService(details),
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
getOwnerProfiles: async (entityId: string): Promise<Profile[]> => {
|
|
144
|
+
const ctx = service.ctx as Context
|
|
145
|
+
const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
|
146
|
+
const { items: profiles } = await profileResource.list({ criteria: { entityId } })
|
|
147
|
+
|
|
148
|
+
return profiles.map(p => ({
|
|
149
|
+
id: p.profileId,
|
|
150
|
+
name: p.name,
|
|
151
|
+
credential: p.credential,
|
|
152
|
+
entityId: p.entityId,
|
|
153
|
+
scopes: p.scopes,
|
|
154
|
+
groups: p.groups,
|
|
155
|
+
permissions: p.permissions,
|
|
156
|
+
attributes: p.attributes,
|
|
157
|
+
}))
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
getOwnerCredentials: async (userId: string, entityId?: string, type?: string): Promise<AuthCredentials | undefined> => {
|
|
161
|
+
const ctx = service.ctx as Context
|
|
162
|
+
const profileResource = ctx.resource<IdentityProfileResource>(AUTH_IDENTITY_PROFILE)
|
|
163
|
+
const credsResource = ctx.resource<IdentityCredentialsResource>(AUTH_IDENTITY_CREDENTIALS)
|
|
164
|
+
|
|
165
|
+
const profileFilter: Record<string, unknown> = { userId }
|
|
166
|
+
if (entityId != null) profileFilter.entityId = entityId
|
|
167
|
+
const { items: profiles } = await profileResource.list(profileFilter as any)
|
|
168
|
+
if (profiles.length === 0) return undefined
|
|
169
|
+
|
|
170
|
+
const credsFilter: Record<string, unknown> = {
|
|
171
|
+
profileId: { $in: profiles.map(p => p.profileId) }
|
|
172
|
+
}
|
|
173
|
+
if (type != null) credsFilter.type = type
|
|
174
|
+
const { items: creds } = await credsResource.list(credsFilter as any)
|
|
175
|
+
if (creds.length === 0) return undefined
|
|
176
|
+
|
|
177
|
+
const cred = creds[0]
|
|
178
|
+
const profile = profiles.find(p => p.profileId === cred.profileId) ?? profiles[0]
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
type: cred.type,
|
|
182
|
+
role: profile.role,
|
|
183
|
+
userId: profile.userId ?? profile.profileId,
|
|
184
|
+
profileId: profile.profileId,
|
|
185
|
+
entityId: profile.entityId,
|
|
186
|
+
scopes: profile.scopes,
|
|
187
|
+
challenge: cred.challenge,
|
|
188
|
+
credential: cred.credential,
|
|
189
|
+
publicKey: cred.publicKey,
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
return service
|
|
195
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ResourceRecord } from '@owlmeans/resource'
|
|
2
|
+
import type { MongoResource } from '@owlmeans/mongo-resource'
|
|
3
|
+
import type { InitializedService } from '@owlmeans/context'
|
|
4
|
+
import type { ServerConfig, ServerContext } from '@owlmeans/server-context'
|
|
5
|
+
import type { AuthCredentials, AuthPayload, AuthRole, Profile } from '@owlmeans/auth'
|
|
6
|
+
import type { ProviderProfileDetails } from '@owlmeans/oidc'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Local account record — one per user.
|
|
10
|
+
* id is the Mongo resource id; credential is the stable unique local entity slug.
|
|
11
|
+
*/
|
|
12
|
+
export interface IdentityAccount extends Profile, ResourceRecord {
|
|
13
|
+
id: string
|
|
14
|
+
credential: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Local profile record — ties a user to an entity with a role.
|
|
19
|
+
* Mirrors IAMProfile from @owlmeans/auth backend.
|
|
20
|
+
*/
|
|
21
|
+
export interface IdentityProfile extends Profile, ResourceRecord {
|
|
22
|
+
id: string
|
|
23
|
+
profileId: string
|
|
24
|
+
userId?: string
|
|
25
|
+
role: AuthRole
|
|
26
|
+
expiresAt?: Date
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provider credentials record — one per provider link per profile.
|
|
31
|
+
* Maps an external provider subject to a local profile using auth-native fields.
|
|
32
|
+
* credential stores the login-service key: "service:{type}:{service}"
|
|
33
|
+
* userId stores the derived external login key: "{type}:{service}:{providerSub}"
|
|
34
|
+
*/
|
|
35
|
+
export interface IdentityCredentials extends AuthCredentials, ResourceRecord {
|
|
36
|
+
profileId: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type IdentityAccountResource = MongoResource<IdentityAccount>
|
|
40
|
+
export type IdentityProfileResource = MongoResource<IdentityProfile>
|
|
41
|
+
export type IdentityCredentialsResource = MongoResource<IdentityCredentials>
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compatible with AccountLinkingService from @owlmeans/server-oidc-rp
|
|
45
|
+
* but defined independently to avoid circular dependency.
|
|
46
|
+
*/
|
|
47
|
+
export interface IdentityLinkingService extends InitializedService {
|
|
48
|
+
getLinkedProfile: (details: ProviderProfileDetails) => Promise<AuthPayload | null>
|
|
49
|
+
linkProfile: (details: ProviderProfileDetails, meta: AccountMeta) => Promise<AuthPayload>
|
|
50
|
+
linkCredentials: (details: ProviderProfileDetails) => Promise<AuthPayload>
|
|
51
|
+
getOwnerProfiles: (entityId: string) => Promise<Profile[]>
|
|
52
|
+
getOwnerCredentials: (userId: string, entityId?: string, type?: string) => Promise<AuthCredentials | undefined>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface AccountMeta {
|
|
56
|
+
username: string
|
|
57
|
+
force?: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface IdentityConfig extends ServerConfig {
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface IdentityContext<C extends IdentityConfig = IdentityConfig> extends ServerContext<C> {
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface GoogleUserInfo {
|
|
67
|
+
sub: string
|
|
68
|
+
email: string
|
|
69
|
+
email_verified?: boolean
|
|
70
|
+
name?: string
|
|
71
|
+
given_name?: string
|
|
72
|
+
family_name?: string
|
|
73
|
+
picture?: string
|
|
74
|
+
}
|
|
75
|
+
|