@owlmeans/server-oidc-rp 0.1.0

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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +520 -0
  3. package/build/.gitkeep +0 -0
  4. package/build/actions/index.d.ts +3 -0
  5. package/build/actions/index.d.ts.map +1 -0
  6. package/build/actions/index.js +3 -0
  7. package/build/actions/index.js.map +1 -0
  8. package/build/actions/init.d.ts +3 -0
  9. package/build/actions/init.d.ts.map +1 -0
  10. package/build/actions/init.js +68 -0
  11. package/build/actions/init.js.map +1 -0
  12. package/build/actions/process.d.ts +17 -0
  13. package/build/actions/process.d.ts.map +1 -0
  14. package/build/actions/process.js +80 -0
  15. package/build/actions/process.js.map +1 -0
  16. package/build/auth/consts.d.ts +2 -0
  17. package/build/auth/consts.d.ts.map +1 -0
  18. package/build/auth/consts.js +2 -0
  19. package/build/auth/consts.js.map +1 -0
  20. package/build/auth/index.d.ts +2 -0
  21. package/build/auth/index.d.ts.map +1 -0
  22. package/build/auth/index.js +2 -0
  23. package/build/auth/index.js.map +1 -0
  24. package/build/auth/plugins/index.d.ts +2 -0
  25. package/build/auth/plugins/index.d.ts.map +1 -0
  26. package/build/auth/plugins/index.js +6 -0
  27. package/build/auth/plugins/index.js.map +1 -0
  28. package/build/auth/plugins/oidc-client.d.ts +50 -0
  29. package/build/auth/plugins/oidc-client.d.ts.map +1 -0
  30. package/build/auth/plugins/oidc-client.js +211 -0
  31. package/build/auth/plugins/oidc-client.js.map +1 -0
  32. package/build/auth/plugins/types.d.ts +1 -0
  33. package/build/auth/plugins/types.d.ts.map +1 -0
  34. package/build/auth/plugins/types.js +2 -0
  35. package/build/auth/plugins/types.js.map +1 -0
  36. package/build/consts.d.ts +20 -0
  37. package/build/consts.d.ts.map +1 -0
  38. package/build/consts.js +20 -0
  39. package/build/consts.js.map +1 -0
  40. package/build/gate.d.ts +3 -0
  41. package/build/gate.d.ts.map +1 -0
  42. package/build/gate.js +22 -0
  43. package/build/gate.js.map +1 -0
  44. package/build/guard.d.ts +6 -0
  45. package/build/guard.d.ts.map +1 -0
  46. package/build/guard.js +13 -0
  47. package/build/guard.js.map +1 -0
  48. package/build/index.d.ts +8 -0
  49. package/build/index.d.ts.map +1 -0
  50. package/build/index.js +7 -0
  51. package/build/index.js.map +1 -0
  52. package/build/model/consts.d.ts +6 -0
  53. package/build/model/consts.d.ts.map +1 -0
  54. package/build/model/consts.js +7 -0
  55. package/build/model/consts.js.map +1 -0
  56. package/build/model/gate.d.ts +4 -0
  57. package/build/model/gate.d.ts.map +1 -0
  58. package/build/model/gate.js +98 -0
  59. package/build/model/gate.js.map +1 -0
  60. package/build/model/types.d.ts +25 -0
  61. package/build/model/types.d.ts.map +1 -0
  62. package/build/model/types.js +2 -0
  63. package/build/model/types.js.map +1 -0
  64. package/build/models.d.ts +1 -0
  65. package/build/models.d.ts.map +1 -0
  66. package/build/models.js +2 -0
  67. package/build/models.js.map +1 -0
  68. package/build/modules.d.ts +3 -0
  69. package/build/modules.d.ts.map +1 -0
  70. package/build/modules.js +12 -0
  71. package/build/modules.js.map +1 -0
  72. package/build/service.d.ts +3 -0
  73. package/build/service.d.ts.map +1 -0
  74. package/build/service.js +186 -0
  75. package/build/service.js.map +1 -0
  76. package/build/types.d.ts +55 -0
  77. package/build/types.d.ts.map +1 -0
  78. package/build/types.js +2 -0
  79. package/build/types.js.map +1 -0
  80. package/build/utils/auth.d.ts +5 -0
  81. package/build/utils/auth.d.ts.map +1 -0
  82. package/build/utils/auth.js +48 -0
  83. package/build/utils/auth.js.map +1 -0
  84. package/build/utils/cache.d.ts +8 -0
  85. package/build/utils/cache.d.ts.map +1 -0
  86. package/build/utils/cache.js +7 -0
  87. package/build/utils/cache.js.map +1 -0
  88. package/build/utils/index.d.ts +5 -0
  89. package/build/utils/index.d.ts.map +1 -0
  90. package/build/utils/index.js +4 -0
  91. package/build/utils/index.js.map +1 -0
  92. package/build/utils/types.d.ts +10 -0
  93. package/build/utils/types.d.ts.map +1 -0
  94. package/build/utils/types.js +2 -0
  95. package/build/utils/types.js.map +1 -0
  96. package/build/utils/wrapped.d.ts +4 -0
  97. package/build/utils/wrapped.d.ts.map +1 -0
  98. package/build/utils/wrapped.js +3 -0
  99. package/build/utils/wrapped.js.map +1 -0
  100. package/build/wrapper.d.ts +3 -0
  101. package/build/wrapper.d.ts.map +1 -0
  102. package/build/wrapper.js +75 -0
  103. package/build/wrapper.js.map +1 -0
  104. package/package.json +71 -0
  105. package/src/actions/index.ts +3 -0
  106. package/src/actions/init.ts +90 -0
  107. package/src/actions/process.ts +113 -0
  108. package/src/auth/consts.ts +2 -0
  109. package/src/auth/index.ts +2 -0
  110. package/src/auth/plugins/index.ts +8 -0
  111. package/src/auth/plugins/oidc-client.ts +246 -0
  112. package/src/auth/plugins/types.ts +0 -0
  113. package/src/consts.ts +26 -0
  114. package/src/gate.ts +29 -0
  115. package/src/guard.ts +25 -0
  116. package/src/index.ts +7 -0
  117. package/src/model/consts.ts +6 -0
  118. package/src/model/gate.ts +129 -0
  119. package/src/model/types.ts +31 -0
  120. package/src/models.ts +1 -0
  121. package/src/modules.ts +30 -0
  122. package/src/service.ts +251 -0
  123. package/src/types.ts +64 -0
  124. package/src/utils/auth.ts +67 -0
  125. package/src/utils/cache.ts +12 -0
  126. package/src/utils/index.ts +5 -0
  127. package/src/utils/types.ts +10 -0
  128. package/src/utils/wrapped.ts +6 -0
  129. package/src/wrapper.ts +92 -0
  130. package/tsconfig.json +15 -0
  131. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,3 @@
1
+ import { WRAPPED_OIDC } from '@owlmeans/oidc';
2
+ export const wrapper = (context) => context.service(WRAPPED_OIDC);
3
+ //# sourceMappingURL=wrapped.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapped.js","sourceRoot":"","sources":["../../src/utils/wrapped.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG7C,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAgB,EAAsB,EAAE,CAC9D,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { WrappedOIDCService } from '@owlmeans/oidc';
2
+ export declare const makeOidcWrappingService: () => WrappedOIDCService;
3
+ //# sourceMappingURL=wrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAWzE,eAAO,MAAM,uBAAuB,QAAO,kBA4E1C,CAAA"}
@@ -0,0 +1,75 @@
1
+ import { AuthenFailed, AuthorizationError, AuthRole } from '@owlmeans/auth';
2
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope';
3
+ import { createService } from '@owlmeans/context';
4
+ import { OIDC_WRAPPED_TOKEN, WRAPPED_OIDC } from '@owlmeans/oidc';
5
+ import { cache, managedId } from './utils/cache.js';
6
+ import { authService, OIDC_AUTH_LIFTETIME, OIDC_WRAP_FRESHNESS } from './consts.js';
7
+ import days from 'dayjs';
8
+ import { TRUSTED } from '@owlmeans/config';
9
+ import { AUTH_SRV_KEY } from '@owlmeans/server-auth';
10
+ import { trust } from '@owlmeans/auth-common/utils';
11
+ export const makeOidcWrappingService = () => {
12
+ const service = createService(WRAPPED_OIDC, {
13
+ update: async (token, thr) => {
14
+ const ctx = service.assertCtx();
15
+ token = typeof token === 'string' ? token : token?.token;
16
+ if (token == null) {
17
+ throw new AuthorizationError('token');
18
+ }
19
+ const [, authentication] = token.split(' ');
20
+ const envelope = makeEnvelopeModel(authentication, EnvelopeKind.Token);
21
+ const user = envelope.message();
22
+ try {
23
+ const record = await cache(ctx).get(managedId(user.token));
24
+ if (record == null || record.payload == null) {
25
+ throw new AuthorizationError('record');
26
+ }
27
+ if (record.validated != null && days(record.validated)
28
+ .add(OIDC_WRAP_FRESHNESS, 'milliseconds').isAfter()) {
29
+ return { token };
30
+ }
31
+ record.validated = new Date();
32
+ const [update] = await ctx.module(authService.auth.update)
33
+ .call({ body: { token, tokenSet: record.payload } });
34
+ const updateEnvelope = makeEnvelopeModel(update.token, EnvelopeKind.Token);
35
+ const authManager = await trust(ctx, TRUSTED, AUTH_SRV_KEY);
36
+ if (!await updateEnvelope.verify(authManager.key)) {
37
+ throw new AuthenFailed();
38
+ }
39
+ const updatedAuth = updateEnvelope.message();
40
+ const updatedUser = {
41
+ ...user,
42
+ role: updatedAuth.role ?? AuthRole.Guest,
43
+ userId: updatedAuth.userId ?? user.userId,
44
+ profileId: updatedAuth.profileId,
45
+ entityId: updatedAuth.entityId ?? user.entityId,
46
+ createdAt: new Date()
47
+ };
48
+ if (updatedAuth.challenge !== user.token) {
49
+ await cache(ctx).delete(record);
50
+ updatedUser.token = updatedAuth.challenge;
51
+ record.id = managedId(updatedUser.token);
52
+ }
53
+ const trusted = await trust(ctx, TRUSTED, ctx.cfg.alias ?? ctx.cfg.service);
54
+ const authorization = await makeEnvelopeModel(OIDC_WRAPPED_TOKEN)
55
+ .send(updatedUser, null).sign(trusted.key, EnvelopeKind.Token);
56
+ record.payload = update.tokenSet;
57
+ await cache(ctx).save(record, { ttl: OIDC_AUTH_LIFTETIME / 1000 });
58
+ return { token: `${OIDC_WRAPPED_TOKEN.toUpperCase()} ${authorization}` };
59
+ }
60
+ catch (err) {
61
+ console.error(err);
62
+ await cache(ctx).delete(managedId(user.token));
63
+ if (thr) {
64
+ if (err instanceof AuthorizationError) {
65
+ throw err;
66
+ }
67
+ throw new AuthorizationError('unknown');
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ });
73
+ return service;
74
+ };
75
+ //# sourceMappingURL=wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@owlmeans/server-oidc-rp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "tsc -b",
7
+ "dev": "sleep 312 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
8
+ "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ },
10
+ "main": "build/index.js",
11
+ "module": "build/index.js",
12
+ "types": "build/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./build/index.js",
16
+ "require": "./build/index.js",
17
+ "default": "./build/index.js",
18
+ "module": "./build/index.js",
19
+ "types": "./build/index.d.ts"
20
+ },
21
+ "./auth/plugins": {
22
+ "import": "./build/auth/plugins/index.js",
23
+ "require": "./build/auth/plugins/index.js",
24
+ "default": "./build/auth/plugins/index.js",
25
+ "module": "./build/auth/plugins/index.js",
26
+ "types": "./build/auth/plugins/index.d.ts"
27
+ },
28
+ "./auth": {
29
+ "import": "./build/auth/index.js",
30
+ "require": "./build/auth/index.js",
31
+ "default": "./build/auth/index.js",
32
+ "module": "./build/auth/index.js",
33
+ "types": "./build/auth/index.d.ts"
34
+ }
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.7.8",
38
+ "nodemon": "^3.1.7",
39
+ "npm-check": "^6.0.1",
40
+ "typescript": "^5.6.3"
41
+ },
42
+ "dependencies": {
43
+ "@noble/hashes": "^1.5.0",
44
+ "@owlmeans/auth": "^0.1.0",
45
+ "@owlmeans/auth-common": "^0.1.0",
46
+ "@owlmeans/basic-envelope": "^0.1.0",
47
+ "@owlmeans/client-module": "^0.1.0",
48
+ "@owlmeans/config": "^0.1.0",
49
+ "@owlmeans/context": "^0.1.0",
50
+ "@owlmeans/did": "^0.1.0",
51
+ "@owlmeans/module": "^0.1.0",
52
+ "@owlmeans/oidc": "^0.1.0",
53
+ "@owlmeans/resource": "^0.1.0",
54
+ "@owlmeans/route": "^0.1.0",
55
+ "@owlmeans/server-api": "^0.1.0",
56
+ "@owlmeans/server-auth": "^0.1.0",
57
+ "@owlmeans/server-context": "^0.1.0",
58
+ "@owlmeans/server-module": "^0.1.0",
59
+ "@scure/base": "^1.1.9",
60
+ "dayjs": "^1.11.13",
61
+ "jose": "^5.9.6",
62
+ "openid-client": "^6.1.1"
63
+ },
64
+ "peerDependencies": {
65
+ "ajv": "*"
66
+ },
67
+ "private": false,
68
+ "publishConfig": {
69
+ "access": "public"
70
+ }
71
+ }
@@ -0,0 +1,3 @@
1
+
2
+ export * from './process.js'
3
+ export * from './init.js'
@@ -0,0 +1,90 @@
1
+ import type { RefedModuleHandler } from '@owlmeans/server-module'
2
+ import { handleBody } from '@owlmeans/server-api'
3
+ import type { OIDCAuthInitParams, OidcProviderDescriptor } from '@owlmeans/oidc'
4
+ import { AuthenPayloadError, AuthUnknown, DISPATCHER } from '@owlmeans/auth'
5
+ import { assertContext } from '@owlmeans/context'
6
+ import type { Config, Context, OidcClientAdapter, OidcClientService } from '../types.js'
7
+ import type { ClientModule } from '@owlmeans/client-module'
8
+ import { authService, DEFAULT_ALIAS } from '../consts.js'
9
+ // import { PROVIDER_CACHE_TTL } from '../consts.js'
10
+ // import type { Client } from 'openid-client'
11
+
12
+ import { base64urlnopad as base64 } from '@scure/base'
13
+ import { randomBytes } from '@noble/hashes/utils'
14
+ import { sha256 } from '@noble/hashes/sha256'
15
+ import { cache, verifierId } from '../utils/cache.js'
16
+ import { AUTHEN_TIMEFRAME } from '@owlmeans/server-auth'
17
+
18
+ export const init: RefedModuleHandler = handleBody(async (body: OIDCAuthInitParams, ctx) => {
19
+ if (body.entity == null) {
20
+ throw new AuthenPayloadError('entity')
21
+ }
22
+
23
+ const entityId = body.entity
24
+
25
+ const context = assertContext<Config, Context>(ctx)
26
+ const oidc = context.service<OidcClientService>(DEFAULT_ALIAS)
27
+
28
+ let client: OidcClientAdapter
29
+ // if (!oidc.hasProvider({ entityId })) {
30
+ /**
31
+ * @TODO We need to move it to some remote resource.
32
+ * And make oidc service itself use such a resource to get required client.
33
+ */
34
+ const [providers] = await context.module<ClientModule<OidcProviderDescriptor[]>>(
35
+ authService.provider.list
36
+ ).call({
37
+ params: { service: context.cfg.alias ?? context.cfg.service },
38
+ query: { entityId }
39
+ })
40
+
41
+ if (providers.length < 1) {
42
+ throw new AuthUnknown()
43
+ }
44
+
45
+ console.log("\n\n ************* \n Providers we get from remote:", providers)
46
+
47
+ const provider = providers.find(p => p.def === true) ?? providers.shift()
48
+
49
+ console.log("\n\nProvider we choose:", provider)
50
+ console.log( "************* \n\n")
51
+
52
+ if (provider == null) {
53
+ throw new AuthUnknown()
54
+ }
55
+ oidc.registerTemporaryProvider(provider)
56
+ client = await oidc.getClient({ clientId: provider.clientId, entityId })
57
+ // Cache provider for a while - we dont actually need to clean it up
58
+ // setTimeout(() => provider && oidc.unregisterTemporaryProvider(provider), PROVIDER_CACHE_TTL)
59
+ // } else {
60
+ // client = await oidc.getClient({
61
+ // entityId, clientId: oidc.entityToClientId({ entityId })
62
+ // })
63
+ // }
64
+
65
+ const verifier = base64.encode(randomBytes(32))
66
+ const challenge = base64.encode(sha256(verifier))
67
+
68
+ console.log("\n\n We create verifierID: ", verifierId(challenge), verifier, " with client: ", client.getClientId(), "\n\n")
69
+
70
+ await cache(context).create({
71
+ id: verifierId(challenge),
72
+ verifier,
73
+ client: client.getClientId(),
74
+ entityId,
75
+ }, { ttl: AUTHEN_TIMEFRAME / 1000 })
76
+
77
+ const [dispatcherUrl] = await context.module<ClientModule<string>>(DISPATCHER).call()
78
+
79
+ const cfg = client.getConfig()
80
+ const url = client.makeAuthUrl({
81
+ scope: `openid profile email ${cfg?.extraScopes ?? ''}`,
82
+ code_challenge: challenge,
83
+ code_challenge_method: 'S256',
84
+ redirect_uri: dispatcherUrl,
85
+ })
86
+
87
+ console.log('Url we got', url)
88
+
89
+ return url
90
+ })
@@ -0,0 +1,113 @@
1
+ import type { RefedModuleHandler } from '@owlmeans/server-module'
2
+ import { handleBody } from '@owlmeans/server-api'
3
+ import type { OIDCClientAuthPayload } from '@owlmeans/oidc'
4
+ import { OIDC_WRAPPED_TOKEN } from '@owlmeans/oidc'
5
+ import { assertContext } from '@owlmeans/context'
6
+ import type { Config, Context } from '../types.js'
7
+ import Url from 'url'
8
+ import { makeOidcAuthentication } from '../utils/auth.js'
9
+ import type { Auth, AuthCredentials } from '@owlmeans/auth'
10
+ import { AuthenFailed, AuthManagerError, AuthRole } from '@owlmeans/auth'
11
+ import { decodeJwt } from 'jose'
12
+ import { cache, exchangeId, managedId } from '../utils/cache.js'
13
+ import { trust } from '@owlmeans/auth-common/utils'
14
+ import { TRUSTED } from '@owlmeans/config'
15
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
16
+ import { OIDC_AUTH_LIFTETIME } from '../consts.js'
17
+ import { wrapper } from '../utils/wrapped.js'
18
+
19
+ /**
20
+ * Authentication method links together OIDC and OwlMeans auth.
21
+ * 1. We authenticate OIDC redirect back params with the classic OIDC client.
22
+ * 2. We temporary store on the server refresh token and OIDC token.
23
+ * 3. We supply the client with perked OwlMeans Auth token that in fact
24
+ * is the set of OIDC tokens (except refresh token - we keep it here?)
25
+ * 4. We used it in a centralized way to check permissions over OwlMeans Auth
26
+ * central service irrespectively is it OIDC or our custom permissions engine
27
+ * (this is useful to properly support paid accounts and bridge the underlying
28
+ * IAM system implemetation - abstract from it).
29
+ * 5. The method ends with production of OwlMeans authorization token.
30
+ *
31
+ * ! There is also token renewal mechanism that should be implemented nearby.
32
+ */
33
+ export const authenticate: RefedModuleHandler = handleBody(async (
34
+ { authUrl, ...params }: OIDCClientAuthPayload,
35
+ ctx
36
+ ) => {
37
+ const context = assertContext<Config, Context>(ctx)
38
+
39
+ const url = new Url.URL(authUrl)
40
+ const redirectUrl = url.searchParams.get('redirect_uri')
41
+
42
+ const [cfg, tokenSet, token] = await makeOidcAuthentication(context)({
43
+ challenge: redirectUrl + ':' + authUrl,
44
+ credential: new Url.URLSearchParams(params).toString()
45
+ } as AuthCredentials)
46
+
47
+
48
+ console.log("\n\n ============ \n Config we got for real autentioncation", cfg)
49
+
50
+ if (tokenSet.id_token == null || tokenSet.access_token == null) {
51
+ throw new AuthenFailed()
52
+ }
53
+
54
+ if (cfg.service == null) {
55
+ throw new AuthManagerError('service')
56
+ }
57
+
58
+ await cache(context).delete(exchangeId(token))
59
+
60
+ console.log(
61
+ 'List provided user permisssions',
62
+ decodeJwt(tokenSet.access_token)
63
+ )
64
+
65
+ const id = decodeJwt(tokenSet.id_token)
66
+ console.log(
67
+ 'token set',
68
+ tokenSet,
69
+ tokenSet.claims(),
70
+ id
71
+ )
72
+
73
+ let user: Auth = {
74
+ type: OIDC_WRAPPED_TOKEN,
75
+ token,
76
+ // @TODO Actually this is highly incorrect - we need to get profile details
77
+ // from the OwlMeans Auth intead ?
78
+ userId: id.sub as string,
79
+ // @TODO Actually we should check is it user or admin or even guest
80
+ // and set proper role
81
+ role: AuthRole.Guest,
82
+ // @TODO It's unclear if it's secure - but it's not used right now
83
+ scopes: [
84
+ ...(context.cfg.shortAlias != null ? [context.cfg.shortAlias] : []),
85
+ ...(context.cfg.alias != null ? [context.cfg.alias] : []),
86
+ context.cfg.service,
87
+ cfg.service,
88
+ ],
89
+ source: `${cfg.service}/${cfg.clientId}${'id' in cfg ? `/${cfg.id}` : ''}`,
90
+ // @TODO Actually this is highly incorrect - we need to get profile details
91
+ // from the OwlMeans Auth intead
92
+ // profileId: user?.userId,
93
+ entityId: cfg.entityId,
94
+ isUser: true,
95
+ createdAt: new Date(),
96
+ }
97
+
98
+ console.log('>>>>> ~~~~~ STORE RECORD WITH ID: ', managedId(token))
99
+ await cache(context).create(
100
+ { id: managedId(token), payload: tokenSet, client: cfg.clientId },
101
+ { ttl: OIDC_AUTH_LIFTETIME / 1000 }
102
+ )
103
+
104
+ const trusted = await trust<Config, Context>(context, TRUSTED, context.cfg.alias ?? context.cfg.service)
105
+ const authorization = await makeEnvelopeModel<Auth>(OIDC_WRAPPED_TOKEN)
106
+ .send(user, null).sign(trusted.key, EnvelopeKind.Token)
107
+
108
+ let auth = { token: `${OIDC_WRAPPED_TOKEN.toUpperCase()} ${authorization}` }
109
+
110
+ auth = await wrapper(context).update(auth, true) ?? auth
111
+
112
+ return auth
113
+ })
@@ -0,0 +1,2 @@
1
+
2
+ export const OIDC_ADMIN_CLIENT = 'admin-cli'
@@ -0,0 +1,2 @@
1
+
2
+ export * from './consts.js'
@@ -0,0 +1,8 @@
1
+
2
+ import { plugins as authPlugins } from '@owlmeans/server-auth/manager'
3
+ import { oidcClientPlugin } from './oidc-client.js'
4
+ import { OIDC_CLIENT_AUTH } from '@owlmeans/oidc'
5
+
6
+ authPlugins[OIDC_CLIENT_AUTH] = oidcClientPlugin as typeof authPlugins[keyof typeof authPlugins]
7
+
8
+ export const plugins = authPlugins
@@ -0,0 +1,246 @@
1
+
2
+ import type { AuthPlugin } from '@owlmeans/server-auth/manager/plugins'
3
+ import type { Config, Context, OidcClientService } from '../../types.js'
4
+ import { assertType } from '@owlmeans/server-auth/manager/plugins'
5
+ import { DEFAULT_ALIAS } from '../../consts.js'
6
+ import { OIDC_CLIENT_AUTH } from '@owlmeans/oidc'
7
+ import type { OidcUserDetails, ProviderProfileDetails } from '@owlmeans/oidc'
8
+ import { base64urlnopad as base64 } from '@scure/base'
9
+ import { randomBytes } from '@noble/hashes/utils'
10
+ import { sha256 } from '@noble/hashes/sha256'
11
+ import { ALL_SCOPES, AuthenFailed, AuthenPayloadError, AuthManagerError, AuthRole } from '@owlmeans/auth'
12
+ import { AUTHEN_TIMEFRAME } from '@owlmeans/server-auth'
13
+ import { decodeJwt } from 'jose'
14
+ import { KEY_OWL } from '@owlmeans/did'
15
+ import { cache, verifierId } from '../../utils/cache.js'
16
+ import { makeOidcAuthentication } from '../../utils/auth.js'
17
+ // import { URL } from 'url'
18
+
19
+ /**
20
+ * Actually the most of implementation desceibed here is a proprietary one and
21
+ * located within OwlMeans Auth service implmentation.
22
+ * We keep it here for easier code navigation and understanding of relying developers
23
+ * about what's going on but we can't show it.
24
+ *
25
+ * @See OwlMeans Auth {AccountLinkingService} interface implementation for more details
26
+ *
27
+ * We need to distinguish the following cases:
28
+ * 1. The user authenticates via owlmenas.org
29
+ * 1.1. The user authenticated in owlmeans.net via IAM product based account.
30
+ * 1.1.1. The user do it for the first time
31
+ * * We create an account
32
+ * * We create and link credentials from jwt to the account (i'm not sure if it's required)
33
+ * * We create the profile
34
+ * * We create and link credentials from jwt to the profile
35
+ * 1.1.2. The user do it not for the first time
36
+ * * We found the linked profile by jwt
37
+ * 1.2. The user autehnticated in owlmeans.net via OwlMeans ID
38
+ * 1.2.1. The user do it for the first time
39
+ * * Figure out if the user has OwlMeans ID linked to the authentication from jwt
40
+ * (GET /admin/realms/{realm}/users/{id}/federated-identity)
41
+ * * We create and link credentials from jwt to the profile
42
+ * 1.2.2. The user do it not for the first time
43
+ * * We found the linked profile by credentials from jwt
44
+ * 2. The user authenticates via a custom identity provider
45
+ * 2.1. The user auhtneticated in his identity provier directly
46
+ * 2.1.1. The user do it for the first time
47
+ * * We create a profile (we cant request info about his Owlmeans id)!
48
+ * * We create and link credentials from jwt to the profile
49
+ * * ! When the user will try to pay for something via this profile
50
+ * * we need to force him to link it to owlmeans.net profile / account.
51
+ * 2.1.2. The user do it not for the first time
52
+ * * We found the linked profile by jwt
53
+ * 2.2. The user authenticated in his identity provider via OwlMeans ID
54
+ * - This case actually shouldn't know anything about it until the profile is linked to the
55
+ * owlmeans.net acccount.
56
+ *
57
+ * Generailized
58
+ * 1. We try to get credentials
59
+ * 2. If there is no credentials we try to get more info abou the user
60
+ * 2.1. To do it we get admin client if it's presented for API access
61
+ * 2.2. If it's presented we get list of identity providers to figure out if there an owlmeans.net id
62
+ * 3. If there is no owlmeans.net account while the login is going through it - we create one
63
+ * 4. If there is no profile - we create one and link oidc credentials
64
+ */
65
+ export const oidcClientPlugin = <C extends Config, T extends Context<C>>(context: T): AuthPlugin => {
66
+ const authenticate = makeOidcAuthentication<C, T>(context)
67
+
68
+ const plugin: AuthPlugin = {
69
+ type: OIDC_CLIENT_AUTH,
70
+
71
+ init: async request => {
72
+ assertType(request.type, plugin)
73
+
74
+ if (context.cfg.oidc.restrictedProviders === false) {
75
+ throw new AuthManagerError('oidc.internal')
76
+ }
77
+
78
+ // @TODO Actually think about how to make oidc service configurable
79
+ const oidc = context.service<OidcClientService>(DEFAULT_ALIAS)
80
+ let entityId = request.entityId ?? oidc.getDefault()
81
+
82
+ if (entityId == null) {
83
+ throw new AuthenPayloadError('client')
84
+ }
85
+
86
+ if (context.cfg.oidc.restrictedProviders === true && oidc.getDefault() !== entityId) {
87
+ throw new AuthenPayloadError('client')
88
+ }
89
+ if (Array.isArray(context.cfg.oidc.restrictedProviders)
90
+ && !context.cfg.oidc.restrictedProviders.includes(entityId)) {
91
+ throw new AuthenPayloadError('client')
92
+ }
93
+
94
+ const verifier = base64.encode(randomBytes(32))
95
+ const challenge = base64.encode(sha256(verifier))
96
+
97
+ console.log("\n\n We create verifierID (2): ", verifierId(challenge), verifier, " with client: ", entityId, "\n\n")
98
+
99
+ await cache<C, T>(context).create({
100
+ id: verifierId(challenge),
101
+ verifier,
102
+ client: entityId
103
+ }, { ttl: AUTHEN_TIMEFRAME / 1000 })
104
+
105
+ if (request.source == null) {
106
+ throw new AuthenPayloadError('redirect-uri')
107
+ }
108
+
109
+ // const redirectUrl = new URL(request.source)
110
+
111
+ // for (let key of redirectUrl.searchParams.keys()) {
112
+ // redirectUrl.searchParams.delete(key)
113
+ // }
114
+
115
+ const client = await oidc.getClient(entityId)
116
+ const cfg = client.getConfig()
117
+ const url = client.makeAuthUrl({
118
+ scope: `openid profile email ${cfg?.extraScopes ?? ''}`,
119
+ code_challenge: challenge,
120
+ code_challenge_method: 'S256',
121
+ redirect_uri: request.source,
122
+ })
123
+
124
+ return { challenge: url }
125
+ },
126
+
127
+ authenticate: async credential => {
128
+ console.log('000000000000000')
129
+ const [cfg, tokenSet, exchangeToken] = await authenticate(credential)
130
+
131
+ if (tokenSet.id_token == null || tokenSet.access_token == null) {
132
+ throw new AuthenFailed()
133
+ }
134
+
135
+ const oidc = context.service<OidcClientService>(DEFAULT_ALIAS)
136
+ const jwt = decodeJwt(tokenSet.id_token)
137
+
138
+ if (jwt.sub == null) {
139
+ throw new AuthenFailed()
140
+ }
141
+
142
+ const store = oidc.accountLinking()
143
+ if (store == null) {
144
+ throw new AuthManagerError('oidc.account.store')
145
+ }
146
+
147
+ if (cfg.service == null) {
148
+ throw new AuthManagerError('oidc.service')
149
+ }
150
+
151
+ let profile = await store.getLinkedProfile({
152
+ type: OIDC_CLIENT_AUTH,
153
+ service: cfg.service,
154
+ clientId: cfg.clientId,
155
+ userId: jwt.sub
156
+ } satisfies ProviderProfileDetails)
157
+
158
+ // @TODO It's likely the case when we are talking about owlmeans.net
159
+ // but some addtional checks need to be done.
160
+ // The problem is that we will make the sold identity providers also
161
+ // managable, so this condition isn't enough to cover described cases.
162
+ let details: OidcUserDetails
163
+ if (cfg.apiClientId != null) {
164
+ console.log(1)
165
+ const apiClient = await oidc.getClient(cfg.apiClientId)
166
+ const adminTokens = await apiClient.grantWithCredentials()
167
+ if (adminTokens.access_token == null) {
168
+ throw new AuthManagerError('iam.admin')
169
+ }
170
+ const api = oidc.providerApi()
171
+ if (api == null) {
172
+ // @TODO And what if don't? Probably we can still do something here
173
+ throw new AuthManagerError('iam.admin.api')
174
+ }
175
+ details = await api.getUserDetails(adminTokens.access_token, jwt.sub)
176
+ } else {
177
+ // @TODO We need to do something here if it's an external identity provider
178
+ // and we couldn't get any addiontal info
179
+ throw new AuthManagerError('implemented')
180
+ }
181
+
182
+ if (
183
+ // Create profile
184
+ profile == null
185
+ // Fix profile if it's not linked properly to OwlMeans Id
186
+ || (details.did != null && details.isOwlMeansId && !profile.profileId?.startsWith(KEY_OWL + ':'))
187
+ ) {
188
+ console.log(2)
189
+ details.isOwlMeansId ??= cfg.entityId != null && cfg.entityId === context.cfg.defaultEntityId
190
+ if (details.isOwlMeansId && context.cfg.defaultEntityId == null) {
191
+ throw new AuthManagerError('iam.governance')
192
+ }
193
+ details.entityId = details.isOwlMeansId ? context.cfg.defaultEntityId : cfg.entityId
194
+ if (details.entityId == null) {
195
+ // @TODO We doesn't support clients and entites that aren't bound to an organization
196
+ // right now. Probably we should. Probably there are some cases when we have to.
197
+ throw new AuthManagerError('iam.entity')
198
+ }
199
+
200
+
201
+ console.log('Supper attention ~%~: ', details.entityId, cfg.entityId, context.cfg.defaultEntityId)
202
+
203
+ // We relink existing profile only if it's not yet did bound
204
+ // and the orgnaization is owlmeans.net
205
+ if (profile == null || (
206
+ details.entityId === context.cfg.defaultEntityId
207
+ // @TODO Figure out more flexible solution.
208
+ // This check prevent a potenatilly vulnarability when a user can
209
+ // connect its own idp to his arbitrary IAM system that is connected as
210
+ // idp to our IAM system, and create an internal idp link that
211
+ // uses owlmeans did as user id of another user to link an arbitrary
212
+ // profile to his or her owlmeans user account and take adavantage of cross login.
213
+ // Probably such cross login accross owner like profiles should be prevented
214
+ // without additional authentication. But for now, this check guaranty that it works
215
+ // only with owlmeans.net idp that is managed by ours.
216
+ && details.entityId === cfg.entityId
217
+ )) {
218
+ console.log(3, details)
219
+ profile = await store.linkProfile({
220
+ ...details,
221
+ service: cfg.service,
222
+ clientId: cfg.clientId,
223
+ type: OIDC_CLIENT_AUTH,
224
+ }, {
225
+ username: jwt.preferred_username as string ?? details.username,
226
+ // @TODO It's important to keep it this way to make registration disabling working
227
+ force: false
228
+ })
229
+ }
230
+ }
231
+ console.log(4)
232
+
233
+ credential.scopes = [ALL_SCOPES]
234
+ credential.source = cfg.clientId
235
+ credential.role = AuthRole.User
236
+ credential.type = OIDC_CLIENT_AUTH
237
+ credential.userId = profile.userId
238
+ credential.profileId = profile.profileId
239
+ credential.entityId = profile.entityId
240
+
241
+ return { token: exchangeToken }
242
+ }
243
+ }
244
+
245
+ return plugin
246
+ }
File without changes