@owlmeans/mui-oidc-rp 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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/build/auth/plugins/google-client.d.ts +3 -0
  4. package/build/auth/plugins/google-client.d.ts.map +1 -0
  5. package/build/auth/plugins/google-client.js +71 -0
  6. package/build/auth/plugins/google-client.js.map +1 -0
  7. package/build/auth/plugins/helpers.d.ts +16 -0
  8. package/build/auth/plugins/helpers.d.ts.map +1 -0
  9. package/build/auth/plugins/helpers.js +31 -0
  10. package/build/auth/plugins/helpers.js.map +1 -0
  11. package/build/auth/plugins/index.d.ts +4 -0
  12. package/build/auth/plugins/index.d.ts.map +1 -0
  13. package/build/auth/plugins/index.js +8 -0
  14. package/build/auth/plugins/index.js.map +1 -0
  15. package/build/auth/plugins/oidc-client.d.ts +3 -0
  16. package/build/auth/plugins/oidc-client.d.ts.map +1 -0
  17. package/build/auth/plugins/oidc-client.js +101 -0
  18. package/build/auth/plugins/oidc-client.js.map +1 -0
  19. package/build/components/dispatcher.d.ts +2 -0
  20. package/build/components/dispatcher.d.ts.map +1 -0
  21. package/build/components/dispatcher.js +58 -0
  22. package/build/components/dispatcher.js.map +1 -0
  23. package/build/components/index.d.ts +2 -0
  24. package/build/components/index.d.ts.map +1 -0
  25. package/build/components/index.js +2 -0
  26. package/build/components/index.js.map +1 -0
  27. package/build/consts.d.ts +7 -0
  28. package/build/consts.d.ts.map +1 -0
  29. package/build/consts.js +8 -0
  30. package/build/consts.js.map +1 -0
  31. package/build/guard.d.ts +7 -0
  32. package/build/guard.d.ts.map +1 -0
  33. package/build/guard.js +19 -0
  34. package/build/guard.js.map +1 -0
  35. package/build/index.d.ts +6 -0
  36. package/build/index.d.ts.map +1 -0
  37. package/build/index.js +5 -0
  38. package/build/index.js.map +1 -0
  39. package/build/service.d.ts +3 -0
  40. package/build/service.d.ts.map +1 -0
  41. package/build/service.js +123 -0
  42. package/build/service.js.map +1 -0
  43. package/build/types.d.ts +31 -0
  44. package/build/types.d.ts.map +1 -0
  45. package/build/types.js +2 -0
  46. package/build/types.js.map +1 -0
  47. package/package.json +64 -0
  48. package/src/auth/plugins/google-client.tsx +94 -0
  49. package/src/auth/plugins/helpers.ts +39 -0
  50. package/src/auth/plugins/index.ts +10 -0
  51. package/src/auth/plugins/oidc-client.tsx +118 -0
  52. package/src/components/dispatcher.tsx +61 -0
  53. package/src/components/index.ts +2 -0
  54. package/src/consts.ts +8 -0
  55. package/src/guard.ts +34 -0
  56. package/src/index.ts +6 -0
  57. package/src/service.ts +159 -0
  58. package/src/types.ts +39 -0
  59. package/tests/context.ts +16 -0
  60. package/tests/google-client.spec.ts +64 -0
  61. package/tests/tsconfig.json +12 -0
  62. package/tsconfig.json +11 -0
@@ -0,0 +1,123 @@
1
+ import { assertContext, createService, HOME } from '@owlmeans/context';
2
+ import { DEFAULT_ALIAS } from './consts.js';
3
+ import { UserManager } from 'oidc-client-ts';
4
+ import { DEFAULT_ALIAS as FLOW_SERVICE } from '@owlmeans/client-flow';
5
+ import { FlowStepMissconfigured, OidcAuthStep, STD_OIDC_FLOW, UnknownFlow } from '@owlmeans/flow';
6
+ import { DISPATCHER_OIDC, DISPATCHER_OIDC_INIT, OIDC_CODE_QUERY } from '@owlmeans/oidc';
7
+ import { AUTH_RESOURCE, USER_ID } from '@owlmeans/client-auth';
8
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope';
9
+ export const makeOidcAuthService = (alias = DEFAULT_ALIAS) => {
10
+ const store = (context) => context.auth().store();
11
+ const storeKey = '_oidc-client-interaction';
12
+ const service = createService(alias, {
13
+ /**
14
+ * This method performs when the IdP returns authentication params.
15
+ */
16
+ dispatch: async (params) => {
17
+ /**
18
+ * 1. Check if there are parameters in the URL for the server side OIDC authentication (e.g. code)
19
+ * 2. Send paramters to finalize authentication (proxy request to the auth server?)
20
+ * 3. If the authentication successful set user state using authentication service
21
+ */
22
+ if (params[OIDC_CODE_QUERY] == null) {
23
+ return false;
24
+ }
25
+ const ctx = service.assertCtx();
26
+ params.authUrl = (await store(ctx).get(storeKey)).authUrl;
27
+ const [authToken] = await ctx.module(DISPATCHER_OIDC)
28
+ .call({ body: params });
29
+ if (authToken.token != null && authToken.token !== '') {
30
+ const authResource = ctx.resource(AUTH_RESOURCE);
31
+ await authResource.save({ id: USER_ID, token: authToken.token });
32
+ const [, authorization] = authToken.token.split(' ');
33
+ const envelope = makeEnvelopeModel(authorization, EnvelopeKind.Token);
34
+ ctx.auth().auth = envelope.message();
35
+ ctx.auth().token = authToken.token;
36
+ return true;
37
+ }
38
+ return false;
39
+ },
40
+ authenticate: async (flow, params) => {
41
+ /**
42
+ * 1. Kick of OIDC authentication if we receive some hints about who or for what organization we'd like to authenticate
43
+ * 2. Request OIDC authentication params from the server using these entity and profile id
44
+ * 3. The server should return an URL to redirect user to
45
+ * 4. Return this user to the component for the further processing (redirection)
46
+ */
47
+ const state = flow.state();
48
+ if (state.flow !== STD_OIDC_FLOW || state.step !== OidcAuthStep.Ephemeral) {
49
+ return null;
50
+ }
51
+ if (params.entity == null && state.entityId == null) {
52
+ return null;
53
+ }
54
+ params.entity ??= state.entityId;
55
+ const ctx = service.assertCtx();
56
+ let [redirectTo] = await ctx.module(DISPATCHER_OIDC_INIT)
57
+ .call({ body: params });
58
+ if (flow.payload().simplified === 'true') {
59
+ redirectTo += '&simplified=true';
60
+ }
61
+ const context = service.assertCtx();
62
+ await store(context).save({ id: storeKey, authUrl: redirectTo });
63
+ return redirectTo;
64
+ },
65
+ proceedToRedirectUrl: async (extras) => {
66
+ const context = assertContext(service.ctx);
67
+ const flow = context.service(FLOW_SERVICE);
68
+ const flowModel = await flow.state();
69
+ if (flowModel == null) {
70
+ throw new UnknownFlow('oidc.dispatch');
71
+ }
72
+ const authorityTransition = flowModel.next();
73
+ flowModel.transit(authorityTransition.transition, true, {
74
+ purpose: extras.purpose,
75
+ simplified: extras.simplified,
76
+ });
77
+ // @TODO I'm not sure that this dirty hack is a correct approach
78
+ if (extras.alias === HOME) {
79
+ const redirectTransition = flowModel.next();
80
+ flowModel.transit(redirectTransition.transition, true, {
81
+ purpose: extras.purpose,
82
+ simplified: extras.simplified
83
+ });
84
+ }
85
+ const redirectUrl = await flow.proceed({ params: { uid: extras.uid } }, true);
86
+ return redirectUrl;
87
+ },
88
+ /**
89
+ * This is client-side only OIDC implementation. It's under constructuion.
90
+ * We stoped on the try to redirect to OIDC provider, but it requires browser integrated
91
+ * cryptography under ssl/tls.
92
+ * @TODO Finish client-side only implementation one day
93
+ */
94
+ dispatchClientOnly: async () => {
95
+ const context = assertContext(service.ctx);
96
+ const flow = context.service(FLOW_SERVICE);
97
+ const flowModel = await flow.state();
98
+ if (flowModel == null) {
99
+ throw new UnknownFlow('oidc.dispatch');
100
+ }
101
+ const authorityTransition = flowModel.next();
102
+ flowModel.transit(authorityTransition.transition, true);
103
+ const authorityStep = flowModel.step();
104
+ if (authorityStep.module == null) {
105
+ throw new FlowStepMissconfigured(authorityStep.step);
106
+ }
107
+ const [authorityUrl] = await context.module(authorityStep.module).call();
108
+ const redirectTransition = flowModel.next();
109
+ flowModel.transit(redirectTransition.transition, true);
110
+ const redirectUrl = await flow.proceed(undefined, true);
111
+ // @TODO To proceed we need to provide client_id some way
112
+ const manager = new UserManager({
113
+ // client_id: context.cfg.oidc.consumer?.clientId ?? '',
114
+ client_id: '',
115
+ redirect_uri: redirectUrl,
116
+ authority: authorityUrl,
117
+ });
118
+ await manager.signinRedirect({ state: flowModel.serialize() });
119
+ }
120
+ });
121
+ return service;
122
+ };
123
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { InitializedService } from '@owlmeans/context';
2
+ import type { AppConfig, AppContext } from '@owlmeans/web-client';
3
+ import type { OIDCAuthInitParams, WithSharedConfig } from '@owlmeans/oidc';
4
+ import type { OidcAuthPurposes } from './consts.js';
5
+ import type { FlowModel, FlowPayload } from '@owlmeans/flow';
6
+ import type { ResourceRecord } from '@owlmeans/resource';
7
+ import type { AuthToken } from '@owlmeans/auth';
8
+ export interface OidcAuthService extends InitializedService {
9
+ dispatch: (params: Record<string, string>) => Promise<boolean>;
10
+ authenticate: (flow: FlowModel, params: OIDCAuthInitParams) => Promise<string | null>;
11
+ proceedToRedirectUrl: (extras: OidcAuthRedirectExtras) => Promise<string>;
12
+ dispatchClientOnly: () => Promise<void>;
13
+ }
14
+ export interface OidcAuthRedirectExtras extends FlowPayload {
15
+ purpose: OidcAuthPurposes;
16
+ simplified?: string;
17
+ uid?: string;
18
+ alias?: string;
19
+ }
20
+ export interface OidcPostAuthPayload extends FlowPayload, AuthToken {
21
+ purpose: OidcAuthPurposes;
22
+ simplified?: string;
23
+ }
24
+ export interface Config extends AppConfig, WithSharedConfig {
25
+ }
26
+ export interface Context<C extends Config = Config> extends AppContext<C> {
27
+ }
28
+ export interface OidcInteraction extends ResourceRecord {
29
+ authUrl: string;
30
+ }
31
+ //# 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,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAE9D,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAGrF,oBAAoB,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAGzE,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAoB,SAAQ,WAAW,EAAG,SAAS;IAClE,OAAO,EAAE,gBAAgB,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,MAAO,SAAQ,SAAS,EAAE,gBAAgB;CAAI;AAE/D,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;CAAI;AAE7E,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,OAAO,EAAE,MAAM,CAAA;CAChB"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@owlmeans/mui-oidc-rp",
3
+ "version": "0.1.3",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -b",
8
+ "dev": "sleep 378 && 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
+ "./auth/plugins": {
24
+ "import": "./build/auth/plugins/index.js",
25
+ "require": "./build/auth/plugins/index.js",
26
+ "default": "./build/auth/plugins/index.js",
27
+ "module": "./build/auth/plugins/index.js",
28
+ "types": "./build/auth/plugins/index.d.ts"
29
+ }
30
+ },
31
+ "dependencies": {
32
+ "@owlmeans/auth": "^0.1.3",
33
+ "@owlmeans/basic-envelope": "^0.1.3",
34
+ "@owlmeans/client": "^0.1.3",
35
+ "@owlmeans/client-auth": "^0.1.3",
36
+ "@owlmeans/client-flow": "^0.1.3",
37
+ "@owlmeans/client-i18n": "^0.1.3",
38
+ "@owlmeans/context": "^0.1.3",
39
+ "@owlmeans/flow": "^0.1.3",
40
+ "@owlmeans/entrypoint": "^0.1.3",
41
+ "@owlmeans/oidc": "^0.1.3",
42
+ "@owlmeans/resource": "^0.1.3",
43
+ "@owlmeans/web-client": "^0.1.3",
44
+ "@owlmeans/web-flow": "^0.1.3",
45
+ "oidc-client-ts": "3.5.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@mui/material": "^7.*",
49
+ "react": "*",
50
+ "react-router": "^7.*"
51
+ },
52
+ "devDependencies": {
53
+ "@owlmeans/dep-config": "workspace:*",
54
+ "@owlmeans/test-auth": "^0.1.2",
55
+ "@types/bun": "^1.3.0",
56
+ "@types/react": "^19.2.7",
57
+ "nodemon": "^3.1.11",
58
+ "npm-check": "^6.0.1",
59
+ "typescript": "^6.0.2"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }
@@ -0,0 +1,94 @@
1
+
2
+ import type { AuthenticationPlugin } from '@owlmeans/client-auth/manager/plugins'
3
+ import { AuthenticationStage, CAUTHEN_AUTHEN_TYPED } from '@owlmeans/auth'
4
+ import type { AuthCredentials } from '@owlmeans/auth'
5
+ import { DEFAULT_ALIAS as AUTH_SERVICE } from '@owlmeans/client-auth'
6
+ import type { AuthService } from '@owlmeans/auth-common'
7
+ import { GOOGLE_CLIENT_AUTH } from '@owlmeans/oidc'
8
+ import { useContext, useValue } from '@owlmeans/client'
9
+ import { HOME } from '@owlmeans/web-client'
10
+ import type { Module } from '@owlmeans/web-client'
11
+ import LinearProgress from '@mui/material/LinearProgress'
12
+ import { extractGoogleUrl, buildCallbackCredentials } from './helpers.js'
13
+
14
+ export const googleClientPlugin: AuthenticationPlugin = {
15
+ type: GOOGLE_CLIENT_AUTH,
16
+
17
+ Implementation: Renderer => ({ type, stage, control }) => {
18
+ const context = useContext()
19
+ Renderer = Renderer ?? googleClientPlugin.Renderer
20
+
21
+ useValue(async cancel => {
22
+ switch (control.stage) {
23
+ case AuthenticationStage.Init: {
24
+ const hasState = await control.hasPersistentState()
25
+ if (cancel?.current) return
26
+
27
+ // Returning from Google redirect — restore state and authenticate
28
+ if (hasState) {
29
+ await control.restore()
30
+ await control.cleanUpState()
31
+
32
+ const url = new URL(window.location.href)
33
+ const code = url.searchParams.get('code')
34
+
35
+ if (code != null) {
36
+ const auth: AuthCredentials = buildCallbackCredentials(
37
+ url.searchParams.toString(),
38
+ type,
39
+ control.allowance?.challenge ?? '',
40
+ )
41
+
42
+ const token = await control.authenticate(auth)
43
+
44
+ if (token.token !== '') {
45
+ const authService = context.service<AuthService>(AUTH_SERVICE)
46
+ await authService.authenticate(token)
47
+ }
48
+
49
+ // Navigate to app root after successful authentication
50
+ const [homeUrl] = await context.module<Module<string>>(HOME).call({ full: true }) ?? []
51
+ window.location.href = homeUrl ?? window.location.origin
52
+
53
+ return
54
+ }
55
+ }
56
+
57
+ // Initial request — ask server for Google auth URL
58
+ const [source] = await context.module<Module<string>>(CAUTHEN_AUTHEN_TYPED).call({
59
+ full: true, params: { type }
60
+ }) ?? []
61
+ await control.requestAllowence({ type, source: source ?? '' })
62
+ break
63
+ }
64
+
65
+ case AuthenticationStage.Authenticate: {
66
+ if (cancel?.current) return
67
+
68
+ if (control.allowance?.challenge != null) {
69
+ // The server wraps challenge as "source:googleUrl" — extract the URL part
70
+ const [source] = await context.module<Module<string>>(CAUTHEN_AUTHEN_TYPED).call({
71
+ full: true, params: { type }
72
+ }) ?? []
73
+ const url = extractGoogleUrl(control.allowance.challenge, source ?? '')
74
+
75
+ // Persist control state before redirect
76
+ await control.persist()
77
+
78
+ // Redirect to Google
79
+ document.location.href = url
80
+ }
81
+ break
82
+ }
83
+ }
84
+ }, [type, stage])
85
+
86
+ if (Renderer == null) {
87
+ throw new SyntaxError('Renderer is not defined for Google plugin')
88
+ }
89
+
90
+ return <Renderer type={type} stage={stage} control={control} params={{}} />
91
+ },
92
+
93
+ Renderer: () => <LinearProgress />
94
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Pure helpers extracted from the Google client plugin for unit-testability.
3
+ * These perform the URL/challenge processing logic that doesn't depend on React.
4
+ */
5
+
6
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
7
+ import { AUTH_SCOPE, AuthRole } from '@owlmeans/auth'
8
+ import type { AuthCredentials } from '@owlmeans/auth'
9
+
10
+ /**
11
+ * Extract the Google auth URL from a signed challenge envelope message.
12
+ * The server wraps the challenge as "source:googleUrl" — this strips the source prefix.
13
+ * Also handles raw Google URLs (rolling deployment compatibility).
14
+ */
15
+ export const extractGoogleUrl = (envelopeChallenge: string, sourcePrefix: string): string => {
16
+ const envelope = makeEnvelopeModel(envelopeChallenge, EnvelopeKind.Wrap)
17
+ const msg = envelope.message<string>(true)
18
+
19
+ if (msg.startsWith(sourcePrefix + ':')) {
20
+ return msg.slice(sourcePrefix.length + 1)
21
+ }
22
+ return msg
23
+ }
24
+
25
+ /**
26
+ * Build AuthCredentials from the Google callback URL query params.
27
+ */
28
+ export const buildCallbackCredentials = (
29
+ queryString: string,
30
+ type: string,
31
+ challenge: string,
32
+ ): AuthCredentials => ({
33
+ type,
34
+ challenge,
35
+ credential: queryString,
36
+ role: AuthRole.User,
37
+ userId: 'code',
38
+ scopes: [AUTH_SCOPE],
39
+ })
@@ -0,0 +1,10 @@
1
+
2
+ import { plugins as authPlugins } from '@owlmeans/client-auth/manager'
3
+ import { OIDC_CLIENT_AUTH, GOOGLE_CLIENT_AUTH } from '@owlmeans/oidc'
4
+ import { oidcClientPlugin } from './oidc-client.js'
5
+ import { googleClientPlugin } from './google-client.js'
6
+
7
+ authPlugins[OIDC_CLIENT_AUTH] = oidcClientPlugin
8
+ authPlugins[GOOGLE_CLIENT_AUTH] = googleClientPlugin
9
+
10
+ export const plugins = authPlugins
@@ -0,0 +1,118 @@
1
+
2
+ import type { AuthenticationPlugin } from '@owlmeans/client-auth/manager/plugins'
3
+ import { AUTH_SCOPE, AuthenticationStage, AuthManagerError, AuthRole } from '@owlmeans/auth'
4
+ import type { AuthCredentials } from '@owlmeans/auth'
5
+ import { OIDC_CLIENT_AUTH } from '@owlmeans/oidc'
6
+ import { useEffect } from 'react'
7
+ import LinearProgress from '@mui/material/LinearProgress'
8
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
9
+ import type { Config, Context, OidcAuthService } from '../../types.js'
10
+ import { useContext } from '@owlmeans/web-client'
11
+ import { DEFAULT_ALIAS, OidcAuthPurposes } from '../../consts.js'
12
+ import { OidcAuthStep, OidpAuthStep, UnknownFlowStep } from '@owlmeans/flow'
13
+ import { useModule } from '@owlmeans/client'
14
+
15
+ export const oidcClientPlugin: AuthenticationPlugin = {
16
+ type: OIDC_CLIENT_AUTH,
17
+
18
+ Implementation: Renderer => ({ type, stage, control }) => {
19
+ const context = useContext<Config, Context>()
20
+ Renderer = Renderer ?? oidcClientPlugin.Renderer
21
+ const module = useModule()
22
+
23
+ useEffect(() => {
24
+ let canceled = false
25
+
26
+ // @TODO extract somewhere else and make it more consequential
27
+ // This way it looks too messy
28
+ switch (control.stage) {
29
+ case AuthenticationStage.Init:
30
+ control.hasPersistentState().then(async hasState => {
31
+ if (canceled) {
32
+ return
33
+ }
34
+ // 3. Properly process additional paramters on sequential rendering to restore control state
35
+ if (hasState) {
36
+ await control.restore()
37
+ await control.cleanUpState()
38
+ const flow = await control.flow()
39
+ if (flow != null) {
40
+ const state = await flow.state()
41
+ if ([OidcAuthStep.PostAuthen, OidpAuthStep.PostAuth].includes(state?.step().step as OidcAuthStep)) {
42
+ const url = new URL(window.location.href)
43
+
44
+ const auth: AuthCredentials = {
45
+ ...control.allowance,
46
+ type,
47
+ challenge: control.allowance?.challenge ?? '',
48
+ credential: url.searchParams.toString(),
49
+ role: AuthRole.User,
50
+ userId: 'code',
51
+ scopes: [AUTH_SCOPE]
52
+ }
53
+
54
+ const tokenized = await control.authenticate(auth)
55
+ // @TODO Introduce some standard further flow in case this plugin/component
56
+ // is used without a callback
57
+ if (tokenized.token !== '') {
58
+ throw new AuthManagerError('callback')
59
+ }
60
+ return // @TODO Will it work here?
61
+ }
62
+ }
63
+ // return
64
+ }
65
+
66
+ // 1. Initialize OIDC server side authentication
67
+ const oidc = context.service<OidcAuthService>(DEFAULT_ALIAS)
68
+
69
+ // @TODO we need to provide an identity provider client as entityId here for flexibel usage
70
+ const flow = await control.flow()
71
+ const state = await flow?.state()
72
+ const _source = await oidc.proceedToRedirectUrl({
73
+ purpose: control.source as OidcAuthPurposes ?? OidcAuthPurposes.Unknown,
74
+ simplified: state?.payload().simplified as string,
75
+ uid: 'uid' in module.params ? `${module.params.uid}` : undefined,
76
+ alias: module.alias
77
+ })
78
+ const source = new URL(_source)
79
+ source.search = ''
80
+ await control.requestAllowence({ type, source: source.toString() })
81
+ })
82
+ case AuthenticationStage.Authenticate:
83
+ control.flow().then(async flow => {
84
+ if (canceled) {
85
+ return
86
+ }
87
+ const flowState = await flow?.state()
88
+ if (flowState == null) {
89
+ throw new UnknownFlowStep(AuthenticationStage.Authenticate)
90
+ }
91
+
92
+ if (control.allowance?.challenge != null) {
93
+ const envelope = makeEnvelopeModel(control.allowance?.challenge, EnvelopeKind.Wrap)
94
+ const challenge = envelope.message<string>(true)
95
+ const parts = challenge.split(':http')
96
+ // This thing includes url with flow state so the OIDC redirect it back a correct way
97
+ const url = 'http' + parts[1]
98
+
99
+ // 2. Include additional parameters (control state with allowance to flow state)
100
+ await control.persist()
101
+
102
+ document.location.href = url
103
+ }
104
+ })
105
+ }
106
+
107
+ return () => { canceled = true }
108
+ }, [type, stage])
109
+
110
+ if (Renderer == null) {
111
+ throw new SyntaxError('Renderer is not defined for OIDC plugin')
112
+ }
113
+
114
+ return <Renderer type={type} stage={stage} control={control} params={module.params} />
115
+ },
116
+
117
+ Renderer: () => <LinearProgress />
118
+ }
@@ -0,0 +1,61 @@
1
+
2
+ import { DispatcherHOC } from '@owlmeans/client-auth'
3
+ import { useSearchParams } from 'react-router'
4
+ import { useEffect } from 'react'
5
+ import { useContext } from '@owlmeans/web-client'
6
+ import { useI18nLib } from '@owlmeans/client-i18n'
7
+ import { AUTH_QUERY } from '@owlmeans/auth'
8
+ import { useFlow } from '@owlmeans/web-flow'
9
+ import { OidcAuthService } from '../types.js'
10
+ import { DEFAULT_ALIAS } from '../consts.js'
11
+
12
+ export const Dispatcher = DispatcherHOC(({ provideToken, navigate }) => {
13
+ const [query] = useSearchParams()
14
+ const context = useContext()
15
+ const client = useFlow()
16
+
17
+ const t = useI18nLib('auth', 'dispatcher')
18
+
19
+ useEffect(() => {
20
+ if (client == null) {
21
+ return
22
+ }
23
+ const token = query.get(AUTH_QUERY)
24
+ const params: Record<string, string> = {}
25
+ query.forEach((value, key) => {
26
+ if (key !== AUTH_QUERY) {
27
+ params[key] = value
28
+ }
29
+ })
30
+
31
+ if (token != null) {
32
+ // This is required for compatibility with a standard OwlMeans Auth flow.
33
+ provideToken({ token }, params)
34
+ } else {
35
+ const oidc = context.service<OidcAuthService>(DEFAULT_ALIAS)
36
+ oidc.dispatch(params).then(async dispatched => {
37
+ if (dispatched) {
38
+ return await navigate()
39
+ }
40
+ if (client == null) {
41
+ return
42
+ }
43
+ const redirect = await oidc.authenticate(client.flow(), params)
44
+ if (redirect != null && redirect !== '') {
45
+ document.location.href = redirect
46
+ return
47
+ }
48
+ const authzToken = await context.auth().authenticated()
49
+ if (authzToken == null) {
50
+ provideToken({ token: '' }, undefined)
51
+ } else {
52
+ await navigate()
53
+ }
54
+ })
55
+ }
56
+ }, [client])
57
+
58
+ return query.has(AUTH_QUERY)
59
+ ? <div>{t('loading')}</div>
60
+ : undefined
61
+ })
@@ -0,0 +1,2 @@
1
+
2
+ export * from './dispatcher.js'
package/src/consts.ts ADDED
@@ -0,0 +1,8 @@
1
+
2
+ export const DEFAULT_ALIAS = 'oidc-rp'
3
+
4
+ export enum OidcAuthPurposes {
5
+ Unknown = 'unknown',
6
+ Subscribe = 'subscribe',
7
+ Login = 'login'
8
+ }
package/src/guard.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { DISPATCHER } from '@owlmeans/auth'
2
+ import { handler } from '@owlmeans/client'
3
+ import type { CommonEntrypoint } from '@owlmeans/entrypoint'
4
+ import type { OidcGuardOptions } from '@owlmeans/oidc'
5
+ import {
6
+ appendOidcGuard as appendBasicOidcGuard,
7
+ DISPATCHER_OIDC, DISPATCHER_OIDC_INIT,
8
+ setupOidcGuard as setupBasicOidcGuard
9
+ } from '@owlmeans/oidc'
10
+ import type { ParametrisedProps } from '@owlmeans/web-client'
11
+ import { elevate, parametriseDispatcher } from '@owlmeans/web-client'
12
+ import { Dispatcher } from './components/dispatcher.js'
13
+ import { makeOidcAuthService } from './service.js'
14
+ import type { Config, Context } from './types.js'
15
+
16
+ export const appendOidcGuard = <C extends Config, T extends Context<C>>(
17
+ context: T, opts?: OidcGuardOptions
18
+ ) => {
19
+ context.registerService(makeOidcAuthService())
20
+
21
+ const ctx = appendBasicOidcGuard<C, T>(context, opts)
22
+
23
+ return ctx
24
+ }
25
+
26
+ export const setupOidcGuard = (modules: CommonEntrypoint[], coguards?: string | string[], extras?: Partial<ParametrisedProps>) => {
27
+ const DispatcherCom = extras ? parametriseDispatcher(extras, Dispatcher) : Dispatcher
28
+
29
+ setupBasicOidcGuard(modules, coguards)
30
+
31
+ elevate(modules, DISPATCHER_OIDC_INIT)
32
+ elevate(modules, DISPATCHER_OIDC)
33
+ elevate(modules, DISPATCHER, handler(DispatcherCom), { force: true })
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+
2
+ export type * from './types.js'
3
+ export * from './consts.js'
4
+ export * from './service.js'
5
+ export * from './guard.js'
6
+ export * from './components/index.js'