@owlmeans/client-auth 0.1.18-rc.2 → 0.1.18-rc.21
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 +6 -6
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client-auth/SKILL.md +134 -21
- package/build/components/dispatcher/component.js +1 -1
- package/build/components/dispatcher/component.js.map +1 -1
- package/build/{modules.d.ts → entrypoints.d.ts} +2 -2
- package/build/entrypoints.d.ts.map +1 -0
- package/build/{modules.js → entrypoints.js} +3 -3
- package/build/entrypoints.js.map +1 -0
- package/build/helper.js +1 -1
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/login/adopt.d.ts +24 -0
- package/build/login/adopt.d.ts.map +1 -0
- package/build/login/adopt.js +28 -0
- package/build/login/adopt.js.map +1 -0
- package/build/login/consts.d.ts +57 -0
- package/build/login/consts.d.ts.map +1 -0
- package/build/login/consts.js +57 -0
- package/build/login/consts.js.map +1 -0
- package/build/login/credit.d.ts +24 -0
- package/build/login/credit.d.ts.map +1 -0
- package/build/login/credit.js +58 -0
- package/build/login/credit.js.map +1 -0
- package/build/login/env.d.ts +32 -0
- package/build/login/env.d.ts.map +1 -0
- package/build/login/env.js +84 -0
- package/build/login/env.js.map +1 -0
- package/build/login/flow.d.ts +20 -0
- package/build/login/flow.d.ts.map +1 -0
- package/build/login/flow.js +39 -0
- package/build/login/flow.js.map +1 -0
- package/build/login/hook.d.ts +37 -0
- package/build/login/hook.d.ts.map +1 -0
- package/build/login/hook.js +72 -0
- package/build/login/hook.js.map +1 -0
- package/build/login/i18n/be.json +37 -0
- package/build/login/i18n/de.json +37 -0
- package/build/login/i18n/en.json +37 -0
- package/build/login/i18n/es.json +37 -0
- package/build/login/i18n/pl.json +37 -0
- package/build/login/i18n/ru.json +37 -0
- package/build/login/i18n/uk.json +37 -0
- package/build/login/i18n.d.ts +2 -0
- package/build/login/i18n.d.ts.map +1 -0
- package/build/login/i18n.js +25 -0
- package/build/login/i18n.js.map +1 -0
- package/build/login/index.d.ts +15 -0
- package/build/login/index.d.ts.map +1 -0
- package/build/login/index.js +15 -0
- package/build/login/index.js.map +1 -0
- package/build/login/methods.d.ts +25 -0
- package/build/login/methods.d.ts.map +1 -0
- package/build/login/methods.js +97 -0
- package/build/login/methods.js.map +1 -0
- package/build/login/resume.d.ts +30 -0
- package/build/login/resume.d.ts.map +1 -0
- package/build/login/resume.js +55 -0
- package/build/login/resume.js.map +1 -0
- package/build/login/screen.d.ts +17 -0
- package/build/login/screen.d.ts.map +1 -0
- package/build/login/screen.js +76 -0
- package/build/login/screen.js.map +1 -0
- package/build/login/service.d.ts +31 -0
- package/build/login/service.d.ts.map +1 -0
- package/build/login/service.js +137 -0
- package/build/login/service.js.map +1 -0
- package/build/login/surrogate.d.ts +20 -0
- package/build/login/surrogate.d.ts.map +1 -0
- package/build/login/surrogate.js +33 -0
- package/build/login/surrogate.js.map +1 -0
- package/build/login/terms.d.ts +20 -0
- package/build/login/terms.d.ts.map +1 -0
- package/build/login/terms.js +0 -0
- package/build/login/terms.js.map +1 -0
- package/build/login/types.d.ts +236 -0
- package/build/login/types.d.ts.map +1 -0
- package/build/login/types.js +23 -0
- package/build/login/types.js.map +1 -0
- package/build/manager/components/authentication/control.js +3 -3
- package/build/manager/components/authentication/control.js.map +1 -1
- package/build/manager/components/authentication/types.d.ts +2 -2
- package/build/manager/components/authentication/types.d.ts.map +1 -1
- package/build/manager/components/tunnel-consumer.js +2 -2
- package/build/manager/components/tunnel-consumer.js.map +1 -1
- package/build/manager/entrypoints.d.ts +2 -0
- package/build/manager/entrypoints.d.ts.map +1 -0
- package/build/manager/{modules.js → entrypoints.js} +4 -4
- package/build/manager/entrypoints.js.map +1 -0
- package/build/manager/plugins/basic-ed25519.d.ts.map +1 -1
- package/build/manager/plugins/basic-ed25519.js +5 -0
- package/build/manager/plugins/basic-ed25519.js.map +1 -1
- package/build/manager/plugins/exports.d.ts +2 -0
- package/build/manager/plugins/exports.d.ts.map +1 -1
- package/build/manager/plugins/exports.js +2 -0
- package/build/manager/plugins/exports.js.map +1 -1
- package/build/manager/plugins/index.d.ts +1 -5
- package/build/manager/plugins/index.d.ts.map +1 -1
- package/build/manager/plugins/index.js +9 -1
- package/build/manager/plugins/index.js.map +1 -1
- package/build/manager/plugins/methods.d.ts +6 -0
- package/build/manager/plugins/methods.d.ts.map +1 -0
- package/build/manager/plugins/methods.js +65 -0
- package/build/manager/plugins/methods.js.map +1 -0
- package/build/manager/plugins/re-captcha.d.ts.map +1 -1
- package/build/manager/plugins/re-captcha.js +5 -0
- package/build/manager/plugins/re-captcha.js.map +1 -1
- package/build/manager/plugins/registry.d.ts +20 -0
- package/build/manager/plugins/registry.d.ts.map +1 -0
- package/build/manager/plugins/registry.js +19 -0
- package/build/manager/plugins/registry.js.map +1 -0
- package/build/manager/plugins/tunnel-consumer.d.ts.map +1 -1
- package/build/manager/plugins/tunnel-consumer.js +4 -0
- package/build/manager/plugins/tunnel-consumer.js.map +1 -1
- package/build/manager/plugins/types.d.ts +53 -0
- package/build/manager/plugins/types.d.ts.map +1 -1
- package/build/service.js +1 -1
- package/build/service.js.map +1 -1
- package/package.json +36 -27
- package/src/components/dispatcher/component.tsx +1 -1
- package/src/{modules.ts → entrypoints.ts} +2 -2
- package/src/helper.ts +1 -1
- package/src/index.ts +1 -1
- package/src/login/adopt.ts +31 -0
- package/src/login/consts.ts +70 -0
- package/src/login/credit.ts +75 -0
- package/src/login/env.ts +86 -0
- package/src/login/flow.ts +44 -0
- package/src/login/hook.ts +80 -0
- package/src/login/i18n/be.json +37 -0
- package/src/login/i18n/de.json +37 -0
- package/src/login/i18n/en.json +37 -0
- package/src/login/i18n/es.json +37 -0
- package/src/login/i18n/pl.json +37 -0
- package/src/login/i18n/ru.json +37 -0
- package/src/login/i18n/uk.json +37 -0
- package/src/login/i18n.ts +26 -0
- package/src/login/index.ts +15 -0
- package/src/login/methods.ts +113 -0
- package/src/login/resume.ts +56 -0
- package/src/login/screen.tsx +136 -0
- package/src/login/service.ts +181 -0
- package/src/login/surrogate.ts +45 -0
- package/src/login/terms.ts +0 -0
- package/src/login/types.ts +258 -0
- package/src/manager/README.md +1 -1
- package/src/manager/components/authentication/control.ts +3 -3
- package/src/manager/components/authentication/types.ts +2 -2
- package/src/manager/components/tunnel-consumer.tsx +2 -2
- package/src/manager/{modules.ts → entrypoints.ts} +3 -3
- package/src/manager/plugins/basic-ed25519.tsx +7 -0
- package/src/manager/plugins/exports.ts +2 -0
- package/src/manager/plugins/index.ts +10 -4
- package/src/manager/plugins/methods.ts +74 -0
- package/src/manager/plugins/re-captcha.tsx +7 -0
- package/src/manager/plugins/registry.ts +26 -0
- package/src/manager/plugins/tunnel-consumer.tsx +6 -0
- package/src/manager/plugins/types.ts +54 -0
- package/src/service.ts +1 -1
- package/tests/context.ts +1 -2
- package/tests/login.spec.ts +313 -0
- package/build/.gitkeep +0 -0
- package/build/manager/modules.d.ts +0 -2
- package/build/manager/modules.d.ts.map +0 -1
- package/build/manager/modules.js.map +0 -1
- package/build/modules.d.ts.map +0 -1
- package/build/modules.js.map +0 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"login": {
|
|
3
|
+
"title": "Вхід",
|
|
4
|
+
"subtitle": "Оберіть спосіб входу.",
|
|
5
|
+
"busy": "Відкриваємо вікно входу…",
|
|
6
|
+
"empty": "Для цього застосунку не налаштовано жодного способу входу.",
|
|
7
|
+
"method": {
|
|
8
|
+
"google": "Продовжити з Google",
|
|
9
|
+
"oidc": "Продовжити з {{provider}}",
|
|
10
|
+
"oidc-default": "Увійти / Зареєструватися",
|
|
11
|
+
"oidc-client": "Продовжити з {{provider}}",
|
|
12
|
+
"basic-ed25519": "Увійти з ключем",
|
|
13
|
+
"pk-supervisor": "Секретний ключ (оператор)",
|
|
14
|
+
"wallet-consumer": "Гаманець OwlMeans ID",
|
|
15
|
+
"one-time-token": "Увійти за кодом з листа",
|
|
16
|
+
"fallback": "Продовжити з {{method}}"
|
|
17
|
+
},
|
|
18
|
+
"terms": {
|
|
19
|
+
"agreement": "Я прочитав(-ла) і приймаю {{terms}} та {{privacy}}.",
|
|
20
|
+
"terms": "Умови користування",
|
|
21
|
+
"privacy": "Політику конфіденційності",
|
|
22
|
+
"cookies": "Політику файлів cookie",
|
|
23
|
+
"required": "Щоб продовжити, підтвердьте Умови користування та Політику конфіденційності."
|
|
24
|
+
},
|
|
25
|
+
"consent": {
|
|
26
|
+
"required": "Вхід зберігає сеансовий файл cookie. Прийміть необхідні файли cookie, щоб продовжити."
|
|
27
|
+
},
|
|
28
|
+
"credit": {
|
|
29
|
+
"powered": "Працює на OwlMeans"
|
|
30
|
+
},
|
|
31
|
+
"error": {
|
|
32
|
+
"failed": "Вхід не завершено. Спробуйте ще раз.",
|
|
33
|
+
"blocked": "Вікно входу заблоковано. Дозвольте спливаючі вікна для цього сайту та спробуйте знову."
|
|
34
|
+
},
|
|
35
|
+
"orphaned": "Вхід виконано. Можете закрити це вікно й повернутися до застосунку."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/login/i18n.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { addI18nLib } from '@owlmeans/i18n';
|
|
2
|
+
import en from './i18n/en.json' with { type: 'json' };
|
|
3
|
+
import pl from './i18n/pl.json' with { type: 'json' };
|
|
4
|
+
import ru from './i18n/ru.json' with { type: 'json' };
|
|
5
|
+
import be from './i18n/be.json' with { type: 'json' };
|
|
6
|
+
import uk from './i18n/uk.json' with { type: 'json' };
|
|
7
|
+
import es from './i18n/es.json' with { type: 'json' };
|
|
8
|
+
import de from './i18n/de.json' with { type: 'json' };
|
|
9
|
+
/**
|
|
10
|
+
* The sign-in screen's copy, registered into the `auth` library resource.
|
|
11
|
+
*
|
|
12
|
+
* `_addI18n` PUSHES rather than replaces, so this coexists with `@owlmeans/web-client`'s own
|
|
13
|
+
* `auth` registration (which owns `dispatcher.*` and `surrogate.*`) and the two merge by tier and
|
|
14
|
+
* priority. Registering here rather than in a UI package is what lets the plain fallback screen
|
|
15
|
+
* and every styled one read the same keys — so the seven-language rule holds on every path a user
|
|
16
|
+
* can actually reach, not only the pretty one.
|
|
17
|
+
*/
|
|
18
|
+
addI18nLib('en', 'auth', en);
|
|
19
|
+
addI18nLib('pl', 'auth', pl);
|
|
20
|
+
addI18nLib('ru', 'auth', ru);
|
|
21
|
+
addI18nLib('be', 'auth', be);
|
|
22
|
+
addI18nLib('uk', 'auth', uk);
|
|
23
|
+
addI18nLib('es', 'auth', es);
|
|
24
|
+
addI18nLib('de', 'auth', de);
|
|
25
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../src/login/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,OAAO,EAAE,MAAM,gBAAgB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAErD;;;;;;;;GAQG;AACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AAC5B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AAC5B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AAC5B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AAC5B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AAC5B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;AAC5B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './i18n.js';
|
|
2
|
+
export * from './consts.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export * from './env.js';
|
|
5
|
+
export * from './adopt.js';
|
|
6
|
+
export * from './methods.js';
|
|
7
|
+
export * from './flow.js';
|
|
8
|
+
export * from './resume.js';
|
|
9
|
+
export * from './terms.js';
|
|
10
|
+
export * from './credit.js';
|
|
11
|
+
export * from './surrogate.js';
|
|
12
|
+
export * from './screen.js';
|
|
13
|
+
export * from './service.js';
|
|
14
|
+
export * from './hook.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAElB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './i18n.js';
|
|
2
|
+
export * from './consts.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export * from './env.js';
|
|
5
|
+
export * from './adopt.js';
|
|
6
|
+
export * from './methods.js';
|
|
7
|
+
export * from './flow.js';
|
|
8
|
+
export * from './resume.js';
|
|
9
|
+
export * from './terms.js';
|
|
10
|
+
export * from './credit.js';
|
|
11
|
+
export * from './surrogate.js';
|
|
12
|
+
export * from './screen.js';
|
|
13
|
+
export * from './service.js';
|
|
14
|
+
export * from './hook.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAElB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { LoginScreenConfig } from '@owlmeans/config';
|
|
2
|
+
import type { LoginMethod, LoginMethodContext, LoginMethodSource } from './types.js';
|
|
3
|
+
export declare const registerMethodSource: (source: LoginMethodSource) => void;
|
|
4
|
+
export declare const listMethodSources: () => LoginMethodSource[];
|
|
5
|
+
/**
|
|
6
|
+
* Everything the current environment may be offered, in the order it should be offered in.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order matters and is not arbitrary:
|
|
9
|
+
*
|
|
10
|
+
* 1. Sources produce candidates. A source that throws is skipped rather than taking the screen
|
|
11
|
+
* down — a misconfigured provider list must not remove the sign-in method that does work.
|
|
12
|
+
* 2. `restricted` candidates are dropped unless the configuration named them, which is what makes
|
|
13
|
+
* an operator login opt-in rather than merely unadvertised.
|
|
14
|
+
* 3. An explicit `methods` list is both a filter AND the order, because an app that bothered to
|
|
15
|
+
* list them means that list.
|
|
16
|
+
*/
|
|
17
|
+
export declare const resolveLoginMethods: (ctx: LoginMethodContext, cfg?: LoginScreenConfig, extra?: LoginMethodSource[]) => LoginMethod[];
|
|
18
|
+
/**
|
|
19
|
+
* The method a screen highlights and focuses.
|
|
20
|
+
*
|
|
21
|
+
* The first `primary` one, else the first offered. It is highlighted, never started: requirement
|
|
22
|
+
* one of this whole feature is that nothing leaves the document without a click.
|
|
23
|
+
*/
|
|
24
|
+
export declare const primaryLoginMethod: (methods: LoginMethod[]) => LoginMethod | null;
|
|
25
|
+
//# sourceMappingURL=methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"methods.d.ts","sourceRoot":"","sources":["../../src/login/methods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAE5E,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAgBpF,eAAO,MAAM,oBAAoB,WAAY,iBAAiB,KAAG,IAMhE,CAAA;AAED,eAAO,MAAM,iBAAiB,QAAO,iBAAiB,EAAkB,CAAA;AAiBxE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,QACzB,kBAAkB,QAAQ,iBAAiB,UAAS,iBAAiB,EAAE,KAC3E,WAAW,EA8Cb,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,YAAa,WAAW,EAAE,KAAG,WAAW,GAAG,IACG,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { DEFAULT_METHOD_ORDER } from './consts.js';
|
|
2
|
+
/**
|
|
3
|
+
* The module-global registry of method SOURCES.
|
|
4
|
+
*
|
|
5
|
+
* A source, rather than a method, because the two things that offer sign-in methods answer
|
|
6
|
+
* different questions at different times: the plugin registry knows what code is loaded, and the
|
|
7
|
+
* OIDC configuration knows what an entity is federated with. Both are answered per render — a
|
|
8
|
+
* provider list can arrive from the api-config middleware after the first paint.
|
|
9
|
+
*
|
|
10
|
+
* Global, like the `AuthenticationPlugin` map it mirrors, because a package registers its source
|
|
11
|
+
* from a side-effect import that has no context to hang it on. A context-scoped registry exists
|
|
12
|
+
* alongside it on the login service, and both are merged by {@link resolveLoginMethods}.
|
|
13
|
+
*/
|
|
14
|
+
const sources = [];
|
|
15
|
+
export const registerMethodSource = (source) => {
|
|
16
|
+
const existing = sources.findIndex(candidate => candidate.alias === source.alias);
|
|
17
|
+
if (existing >= 0) {
|
|
18
|
+
sources.splice(existing, 1);
|
|
19
|
+
}
|
|
20
|
+
sources.push(source);
|
|
21
|
+
};
|
|
22
|
+
export const listMethodSources = () => [...sources];
|
|
23
|
+
/** Normalize a config entry, which may be a bare id or a full override object. */
|
|
24
|
+
const asConfig = (entry) => typeof entry === 'string' ? { id: entry } : entry;
|
|
25
|
+
const merge = (method, override) => override == null ? method : {
|
|
26
|
+
...method,
|
|
27
|
+
...(override.label != null ? { label: override.label } : {}),
|
|
28
|
+
...(override.i18nKey != null ? { i18nKey: override.i18nKey } : {}),
|
|
29
|
+
...(override.icon != null ? { icon: override.icon } : {}),
|
|
30
|
+
...(override.order != null ? { order: override.order } : {}),
|
|
31
|
+
...(override.emphasis != null ? { emphasis: override.emphasis } : {}),
|
|
32
|
+
...(override.params != null ? { params: { ...method.params, ...override.params } } : {}),
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Everything the current environment may be offered, in the order it should be offered in.
|
|
36
|
+
*
|
|
37
|
+
* Resolution order matters and is not arbitrary:
|
|
38
|
+
*
|
|
39
|
+
* 1. Sources produce candidates. A source that throws is skipped rather than taking the screen
|
|
40
|
+
* down — a misconfigured provider list must not remove the sign-in method that does work.
|
|
41
|
+
* 2. `restricted` candidates are dropped unless the configuration named them, which is what makes
|
|
42
|
+
* an operator login opt-in rather than merely unadvertised.
|
|
43
|
+
* 3. An explicit `methods` list is both a filter AND the order, because an app that bothered to
|
|
44
|
+
* list them means that list.
|
|
45
|
+
*/
|
|
46
|
+
export const resolveLoginMethods = (ctx, cfg, extra = []) => {
|
|
47
|
+
const candidates = [];
|
|
48
|
+
for (const source of [...sources, ...extra]) {
|
|
49
|
+
try {
|
|
50
|
+
candidates.push(...source.list(ctx));
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.error(`login: method source "${source.alias}" failed`, e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const overrides = cfg?.overrides ?? {};
|
|
57
|
+
const listed = cfg?.methods?.map(asConfig);
|
|
58
|
+
const listedIds = listed?.map(entry => entry.id).filter((id) => id != null);
|
|
59
|
+
const offered = candidates
|
|
60
|
+
.filter(method => {
|
|
61
|
+
const override = overrides[method.id] ?? listed?.find(entry => entry.id === method.id);
|
|
62
|
+
if (override?.enabled === false || override?.hidden === true) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// A restricted method is offered only where the configuration asked for it — by naming it in
|
|
66
|
+
// `methods`, or by enabling it in `overrides`. Registering the plugin is deliberately not
|
|
67
|
+
// enough: an operator login that appears wherever its code happens to be bundled is an
|
|
68
|
+
// operator login in production.
|
|
69
|
+
if (method.restricted === true
|
|
70
|
+
&& listedIds?.includes(method.id) !== true
|
|
71
|
+
&& override?.enabled !== true) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
})
|
|
76
|
+
.map(method => merge(method, overrides[method.id]));
|
|
77
|
+
if (listedIds != null) {
|
|
78
|
+
return listedIds
|
|
79
|
+
.map(id => {
|
|
80
|
+
const method = offered.find(candidate => candidate.id === id);
|
|
81
|
+
return method != null ? merge(method, listed?.find(entry => entry.id === id)) : null;
|
|
82
|
+
})
|
|
83
|
+
.filter((method) => method != null);
|
|
84
|
+
}
|
|
85
|
+
return offered.sort((a, b) => {
|
|
86
|
+
const order = (a.order ?? DEFAULT_METHOD_ORDER) - (b.order ?? DEFAULT_METHOD_ORDER);
|
|
87
|
+
return order !== 0 ? order : a.id.localeCompare(b.id);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The method a screen highlights and focuses.
|
|
92
|
+
*
|
|
93
|
+
* The first `primary` one, else the first offered. It is highlighted, never started: requirement
|
|
94
|
+
* one of this whole feature is that nothing leaves the document without a click.
|
|
95
|
+
*/
|
|
96
|
+
export const primaryLoginMethod = (methods) => methods.find(method => method.emphasis === 'primary') ?? methods[0] ?? null;
|
|
97
|
+
//# sourceMappingURL=methods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"methods.js","sourceRoot":"","sources":["../../src/login/methods.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { LoginOutcome } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The message key a FINISHED sign-in attempt should show, or null when it actually went somewhere.
|
|
4
|
+
*
|
|
5
|
+
* A dispatcher may treat `Passed` as "carry on with the ordinary continuation", because it has one.
|
|
6
|
+
* A screen does not: the user clicked, the document did not move, and nothing rendered — which is
|
|
7
|
+
* indistinguishable from a broken button, and is exactly how a null authorization URL used to
|
|
8
|
+
* present itself. Every outcome that leaves the user looking at the same screen therefore has to
|
|
9
|
+
* say something.
|
|
10
|
+
*/
|
|
11
|
+
export declare const loginAttemptError: (outcome: LoginOutcome | null) => string | null;
|
|
12
|
+
/** What a dispatcher does once `login().resume(...)` has answered. */
|
|
13
|
+
export declare enum ResumeAction {
|
|
14
|
+
/** The plugin took it over — the browser is leaving, or the window is closing. */
|
|
15
|
+
Stop = "stop",
|
|
16
|
+
/** Render the outcome to the user; there is nothing further to do automatically. */
|
|
17
|
+
Render = "render",
|
|
18
|
+
/** Ordinary tab: keep the session and carry on to the application. */
|
|
19
|
+
Navigate = "navigate"
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The one reading of a `resume` outcome, shared by every dispatcher.
|
|
23
|
+
*
|
|
24
|
+
* Exported as a pure function because three dispatchers — `web-client`, `web-oidc-rp` and
|
|
25
|
+
* `mui-oidc-rp` — have to agree on it, and the last time they each held their own copy of a
|
|
26
|
+
* decision they drifted, which is how a popup came to render the application inside itself in two
|
|
27
|
+
* packages at once. It is also the whole rule, so it is testable without a DOM.
|
|
28
|
+
*/
|
|
29
|
+
export declare const resumeAction: (outcome: LoginOutcome) => ResumeAction;
|
|
30
|
+
//# sourceMappingURL=resume.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/login/resume.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,YAAa,YAAY,GAAG,IAAI,KAAG,MAAM,GAAG,IAYzE,CAAA;AAED,sEAAsE;AACtE,oBAAY,YAAY;IACtB,kFAAkF;IAClF,IAAI,SAAS;IACb,oFAAoF;IACpF,MAAM,WAAW;IACjB,sEAAsE;IACtE,QAAQ,aAAa;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,YAAa,YAAY,KAAG,YAYpD,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LoginOutcome } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The message key a FINISHED sign-in attempt should show, or null when it actually went somewhere.
|
|
4
|
+
*
|
|
5
|
+
* A dispatcher may treat `Passed` as "carry on with the ordinary continuation", because it has one.
|
|
6
|
+
* A screen does not: the user clicked, the document did not move, and nothing rendered — which is
|
|
7
|
+
* indistinguishable from a broken button, and is exactly how a null authorization URL used to
|
|
8
|
+
* present itself. Every outcome that leaves the user looking at the same screen therefore has to
|
|
9
|
+
* say something.
|
|
10
|
+
*/
|
|
11
|
+
export const loginAttemptError = (outcome) => {
|
|
12
|
+
switch (outcome) {
|
|
13
|
+
case LoginOutcome.Gesture:
|
|
14
|
+
// The window never opened. That is the popup blocker, and it has its own copy.
|
|
15
|
+
return 'login.error.blocked';
|
|
16
|
+
case LoginOutcome.Failed:
|
|
17
|
+
case LoginOutcome.Passed:
|
|
18
|
+
return 'login.error.failed';
|
|
19
|
+
default:
|
|
20
|
+
// Handled, Redirected and Orphaned all did something; Orphaned reports itself elsewhere.
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** What a dispatcher does once `login().resume(...)` has answered. */
|
|
25
|
+
export var ResumeAction;
|
|
26
|
+
(function (ResumeAction) {
|
|
27
|
+
/** The plugin took it over — the browser is leaving, or the window is closing. */
|
|
28
|
+
ResumeAction["Stop"] = "stop";
|
|
29
|
+
/** Render the outcome to the user; there is nothing further to do automatically. */
|
|
30
|
+
ResumeAction["Render"] = "render";
|
|
31
|
+
/** Ordinary tab: keep the session and carry on to the application. */
|
|
32
|
+
ResumeAction["Navigate"] = "navigate";
|
|
33
|
+
})(ResumeAction || (ResumeAction = {}));
|
|
34
|
+
/**
|
|
35
|
+
* The one reading of a `resume` outcome, shared by every dispatcher.
|
|
36
|
+
*
|
|
37
|
+
* Exported as a pure function because three dispatchers — `web-client`, `web-oidc-rp` and
|
|
38
|
+
* `mui-oidc-rp` — have to agree on it, and the last time they each held their own copy of a
|
|
39
|
+
* decision they drifted, which is how a popup came to render the application inside itself in two
|
|
40
|
+
* packages at once. It is also the whole rule, so it is testable without a DOM.
|
|
41
|
+
*/
|
|
42
|
+
export const resumeAction = (outcome) => {
|
|
43
|
+
switch (outcome) {
|
|
44
|
+
case LoginOutcome.Handled:
|
|
45
|
+
case LoginOutcome.Redirected:
|
|
46
|
+
return ResumeAction.Stop;
|
|
47
|
+
case LoginOutcome.Orphaned:
|
|
48
|
+
case LoginOutcome.Failed:
|
|
49
|
+
case LoginOutcome.Gesture:
|
|
50
|
+
return ResumeAction.Render;
|
|
51
|
+
default:
|
|
52
|
+
return ResumeAction.Navigate;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=resume.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.js","sourceRoot":"","sources":["../../src/login/resume.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAA4B,EAAiB,EAAE;IAC/E,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,YAAY,CAAC,OAAO;YACvB,+EAA+E;YAC/E,OAAO,qBAAqB,CAAA;QAC9B,KAAK,YAAY,CAAC,MAAM,CAAC;QACzB,KAAK,YAAY,CAAC,MAAM;YACtB,OAAO,oBAAoB,CAAA;QAC7B;YACE,yFAAyF;YACzF,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC,CAAA;AAED,sEAAsE;AACtE,MAAM,CAAN,IAAY,YAOX;AAPD,WAAY,YAAY;IACtB,kFAAkF;IAClF,6BAAa,CAAA;IACb,oFAAoF;IACpF,iCAAiB,CAAA;IACjB,sEAAsE;IACtE,qCAAqB,CAAA;AACvB,CAAC,EAPW,YAAY,KAAZ,YAAY,QAOvB;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAqB,EAAgB,EAAE;IAClE,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,YAAY,CAAC,OAAO,CAAC;QAC1B,KAAK,YAAY,CAAC,UAAU;YAC1B,OAAO,YAAY,CAAC,IAAI,CAAA;QAC1B,KAAK,YAAY,CAAC,QAAQ,CAAC;QAC3B,KAAK,YAAY,CAAC,MAAM,CAAC;QACzB,KAAK,YAAY,CAAC,OAAO;YACvB,OAAO,YAAY,CAAC,MAAM,CAAA;QAC5B;YACE,OAAO,YAAY,CAAC,QAAQ,CAAA;IAChC,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import type { LoginScreenProps } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The sign-in screen every application gets, whether or not it registered a styled one.
|
|
5
|
+
*
|
|
6
|
+
* It exists because a relying party must render SOMETHING rather than start a flow on its own, and
|
|
7
|
+
* a relying party may not depend on a UI family. So this is deliberately plain: raw elements and
|
|
8
|
+
* inline styles, no Tailwind class that a consumer's stylesheet would have to scan for, no shadcn
|
|
9
|
+
* primitive a consumer would have to vendor. An application that registers a real screen through
|
|
10
|
+
* `login().registerScreen(...)` never sees it.
|
|
11
|
+
*
|
|
12
|
+
* Because it is the floor rather than the design, it still has to be correct: it offers the same
|
|
13
|
+
* methods, enforces the same terms confirmation, centres the same way, and renders the same
|
|
14
|
+
* credit line.
|
|
15
|
+
*/
|
|
16
|
+
export declare const FallbackLoginScreen: FC<LoginScreenProps>;
|
|
17
|
+
//# sourceMappingURL=screen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/login/screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,EAAE,EAAE,MAAM,OAAO,CAAA;AAG9C,OAAO,KAAK,EAA6B,gBAAgB,EAAgB,MAAM,YAAY,CAAA;AA+B3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,gBAAgB,CAuFpD,CAAA"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { useContext } from '@owlmeans/client';
|
|
4
|
+
import { LOGIN_SERVICE } from './consts.js';
|
|
5
|
+
import { primaryLoginMethod } from './methods.js';
|
|
6
|
+
import { acceptTerms, resolveTerms, termsAccepted } from './terms.js';
|
|
7
|
+
import { resolveCredit } from './credit.js';
|
|
8
|
+
/**
|
|
9
|
+
* The page the card sits in.
|
|
10
|
+
*
|
|
11
|
+
* It carries its own viewport height because this screen is rendered straight out of the
|
|
12
|
+
* dispatcher, into whatever the application happens to have around it — which is usually nothing
|
|
13
|
+
* with a height, so a percentage minimum would resolve to zero and leave the card at the top.
|
|
14
|
+
*/
|
|
15
|
+
const page = {
|
|
16
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
17
|
+
minHeight: '100dvh', padding: '1rem',
|
|
18
|
+
fontFamily: 'system-ui, sans-serif', lineHeight: 1.5,
|
|
19
|
+
};
|
|
20
|
+
// Centred throughout, so the plain screen and the styled one read the same way.
|
|
21
|
+
const box = {
|
|
22
|
+
width: '100%', maxWidth: '24rem', padding: '1.5rem', textAlign: 'center',
|
|
23
|
+
};
|
|
24
|
+
const button = (emphasis) => ({
|
|
25
|
+
display: 'block', width: '100%', marginTop: '.5rem', padding: '.625rem 1rem',
|
|
26
|
+
fontSize: '1rem', cursor: 'pointer', borderRadius: '.375rem',
|
|
27
|
+
border: '1px solid currentColor',
|
|
28
|
+
...(emphasis === 'primary' ? { fontWeight: 600 } : {}),
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* The sign-in screen every application gets, whether or not it registered a styled one.
|
|
32
|
+
*
|
|
33
|
+
* It exists because a relying party must render SOMETHING rather than start a flow on its own, and
|
|
34
|
+
* a relying party may not depend on a UI family. So this is deliberately plain: raw elements and
|
|
35
|
+
* inline styles, no Tailwind class that a consumer's stylesheet would have to scan for, no shadcn
|
|
36
|
+
* primitive a consumer would have to vendor. An application that registers a real screen through
|
|
37
|
+
* `login().registerScreen(...)` never sees it.
|
|
38
|
+
*
|
|
39
|
+
* Because it is the floor rather than the design, it still has to be correct: it offers the same
|
|
40
|
+
* methods, enforces the same terms confirmation, centres the same way, and renders the same
|
|
41
|
+
* credit line.
|
|
42
|
+
*/
|
|
43
|
+
export const FallbackLoginScreen = props => {
|
|
44
|
+
const context = useContext();
|
|
45
|
+
const t = props.translate ?? ((_key, defaultValue) => defaultValue);
|
|
46
|
+
const cfg = props.config ?? context.cfg.security?.auth?.login;
|
|
47
|
+
const brand = context.cfg.brand;
|
|
48
|
+
const login = context.service(LOGIN_SERVICE);
|
|
49
|
+
const env = login.env();
|
|
50
|
+
const resolved = resolveTerms(props.terms ?? cfg?.terms);
|
|
51
|
+
const credit = resolveCredit(cfg?.credit, brand, context.cfg.service);
|
|
52
|
+
const [accepted, setAccepted] = useState(() => termsAccepted(resolved));
|
|
53
|
+
const [attempted, setAttempted] = useState(false);
|
|
54
|
+
const all = login.methods({ context, env });
|
|
55
|
+
const methods = typeof props.methods === 'function'
|
|
56
|
+
? props.methods(all)
|
|
57
|
+
: props.methods ?? all;
|
|
58
|
+
const primary = primaryLoginMethod(methods);
|
|
59
|
+
const blocked = resolved != null && resolved.required && !accepted;
|
|
60
|
+
const select = useCallback((method) => {
|
|
61
|
+
if (blocked) {
|
|
62
|
+
setAttempted(true);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
void method.start({ context, env });
|
|
66
|
+
}, [blocked, context, env]);
|
|
67
|
+
const onAccept = useCallback((value) => {
|
|
68
|
+
setAccepted(value);
|
|
69
|
+
setAttempted(false);
|
|
70
|
+
acceptTerms(resolved, value);
|
|
71
|
+
}, [resolved]);
|
|
72
|
+
return _jsx("div", { "data-login-screen": true, style: page, children: _jsxs("div", { style: box, children: [_jsx("h1", { style: { fontSize: '1.25rem', marginBottom: '.25rem' }, children: props.title ?? t('login.title', 'Sign in') }), _jsx("p", { style: { opacity: .75, marginTop: 0 }, children: props.subtitle ?? t('login.subtitle', 'Choose how you would like to continue.') }), methods.length < 1
|
|
73
|
+
? _jsx("p", { role: "status", children: t('login.empty', 'No sign-in method is configured for this application.') })
|
|
74
|
+
: methods.map(method => _jsx("button", { type: "button", "data-login-method": method.id, "aria-disabled": blocked, "data-blocked": blocked ? 'true' : undefined, style: { ...button(method.emphasis), opacity: blocked ? .6 : 1 }, autoFocus: method.id === primary?.id, onClick: () => select(method), children: method.label ?? t(`login.method.${method.i18nKey ?? method.id}`, method.id) }, method.id)), resolved != null && _jsxs("p", { style: { marginTop: '1rem', fontSize: '.875rem' }, children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: accepted, "data-login-terms": true, onChange: event => onAccept(event.target.checked) }), ' ', t('login.terms.agreement', 'I have read and agree to the Terms & Conditions and the Privacy Policy.')] }), ' ', _jsx("a", { href: resolved.terms, target: "_blank", rel: "noreferrer noopener", children: t('login.terms.terms', 'Terms & Conditions') }), ' · ', _jsx("a", { href: resolved.privacy, target: "_blank", rel: "noreferrer noopener", children: t('login.terms.privacy', 'Privacy Policy') })] }), attempted && blocked && _jsx("p", { role: "alert", style: { color: '#b00', fontSize: '.875rem' }, children: t('login.terms.required', 'Please confirm the Terms & Conditions and the Privacy Policy to continue.') }), props.footer ?? _jsxs("p", { style: { marginTop: '2rem', fontSize: '.75rem', opacity: .7 }, children: [credit.poweredBy && _jsx("span", { children: t('login.credit.powered', 'Powered by OwlMeans') }), credit.poweredBy && credit.line != null && ' · ', credit.line] })] }) });
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=screen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/login/screen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BasicConfig, BasicContext } from '@owlmeans/context';
|
|
2
|
+
import { LoginOutcome } from './types.js';
|
|
3
|
+
import type { LoginService, LoginServiceAppend } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Login service = plugin host. It holds a registry of login plugins and, on every facade call,
|
|
6
|
+
* resolves the active plugin by cascade (priority desc, first whose `match(env)` is truthy) and
|
|
7
|
+
* delegates to it.
|
|
8
|
+
*
|
|
9
|
+
* The facade methods stay ordinary writable properties (never getters) so that non-plugin
|
|
10
|
+
* implementations — e.g. a native login service — can keep overriding them directly.
|
|
11
|
+
*
|
|
12
|
+
* It is a LAZY service on purpose. The host carries no async setup, and plugin packages must be
|
|
13
|
+
* able to reach it from an app's `makeContext` — i.e. while the context is still in the Loading
|
|
14
|
+
* stage. `context.service()` throws for an uninitialized non-lazy service, which would make
|
|
15
|
+
* `ensureLoginService` fail exactly where apps are told to call it.
|
|
16
|
+
*
|
|
17
|
+
* `begin` and `logout` are deliberately NOT `async`. An async facade method defers the plugin's
|
|
18
|
+
* body past a microtask boundary, and a `window.open` that lands after the user gesture has
|
|
19
|
+
* finished being handled is eaten by the popup blocker. The same rule binds every plugin's own
|
|
20
|
+
* `begin` and `logout`, and it is why a precondition must be synchronous too.
|
|
21
|
+
*/
|
|
22
|
+
export declare const makeLoginService: (alias?: string) => LoginService;
|
|
23
|
+
/**
|
|
24
|
+
* Idempotently obtain the login service on a context, registering an empty host if none exists
|
|
25
|
+
* yet. Plugin packages call this before `registerPlugin`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ensureLoginService: (ctx: BasicContext<any>) => LoginService;
|
|
28
|
+
/** Register the host and expose it as `context.login()`. */
|
|
29
|
+
export declare const appendLogin: <C extends BasicConfig, T extends BasicContext<C>>(ctx: T) => T & LoginServiceAppend;
|
|
30
|
+
export { LoginOutcome };
|
|
31
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/login/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAMlE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,KAAK,EAEV,YAAY,EAAE,kBAAkB,EACjC,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gBAAgB,WAAW,MAAM,KAAmB,YA0HhE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAS,YAAY,CAAC,GAAG,CAAC,KAAG,YAQ3D,CAAA;AAED,4DAA4D;AAC5D,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,OACrE,CAAC,KACL,CAAC,GAAG,kBAMN,CAAA;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createLazyService } from '@owlmeans/context';
|
|
2
|
+
import { DEFAULT_ALIAS } from './consts.js';
|
|
3
|
+
import { defaultLoginEnv } from './env.js';
|
|
4
|
+
import { adoptToken, revokeToken } from './adopt.js';
|
|
5
|
+
import { resolveLoginMethods } from './methods.js';
|
|
6
|
+
import { LoginOutcome } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Login service = plugin host. It holds a registry of login plugins and, on every facade call,
|
|
9
|
+
* resolves the active plugin by cascade (priority desc, first whose `match(env)` is truthy) and
|
|
10
|
+
* delegates to it.
|
|
11
|
+
*
|
|
12
|
+
* The facade methods stay ordinary writable properties (never getters) so that non-plugin
|
|
13
|
+
* implementations — e.g. a native login service — can keep overriding them directly.
|
|
14
|
+
*
|
|
15
|
+
* It is a LAZY service on purpose. The host carries no async setup, and plugin packages must be
|
|
16
|
+
* able to reach it from an app's `makeContext` — i.e. while the context is still in the Loading
|
|
17
|
+
* stage. `context.service()` throws for an uninitialized non-lazy service, which would make
|
|
18
|
+
* `ensureLoginService` fail exactly where apps are told to call it.
|
|
19
|
+
*
|
|
20
|
+
* `begin` and `logout` are deliberately NOT `async`. An async facade method defers the plugin's
|
|
21
|
+
* body past a microtask boundary, and a `window.open` that lands after the user gesture has
|
|
22
|
+
* finished being handled is eaten by the popup blocker. The same rule binds every plugin's own
|
|
23
|
+
* `begin` and `logout`, and it is why a precondition must be synchronous too.
|
|
24
|
+
*/
|
|
25
|
+
export const makeLoginService = (alias = DEFAULT_ALIAS) => {
|
|
26
|
+
const plugins = [];
|
|
27
|
+
const preconditions = [];
|
|
28
|
+
const methodSources = [];
|
|
29
|
+
let screen = null;
|
|
30
|
+
const ctx = () => service.ctx;
|
|
31
|
+
const service = createLazyService(alias, {
|
|
32
|
+
registerPlugin: (plugin) => {
|
|
33
|
+
const existing = plugins.findIndex(candidate => candidate.alias === plugin.alias);
|
|
34
|
+
if (existing >= 0) {
|
|
35
|
+
plugins.splice(existing, 1);
|
|
36
|
+
}
|
|
37
|
+
plugins.push(plugin);
|
|
38
|
+
plugins.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
39
|
+
},
|
|
40
|
+
registerPrecondition: (precondition) => {
|
|
41
|
+
const existing = preconditions.findIndex(candidate => candidate.alias === precondition.alias);
|
|
42
|
+
if (existing >= 0) {
|
|
43
|
+
preconditions.splice(existing, 1);
|
|
44
|
+
}
|
|
45
|
+
preconditions.push(precondition);
|
|
46
|
+
preconditions.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
47
|
+
},
|
|
48
|
+
registerMethodSource: (source) => {
|
|
49
|
+
const existing = methodSources.findIndex(candidate => candidate.alias === source.alias);
|
|
50
|
+
if (existing >= 0) {
|
|
51
|
+
methodSources.splice(existing, 1);
|
|
52
|
+
}
|
|
53
|
+
methodSources.push(source);
|
|
54
|
+
},
|
|
55
|
+
methods: methodCtx => resolveLoginMethods(methodCtx, methodCtx.context.cfg.security?.auth?.login, methodSources),
|
|
56
|
+
registerScreen: value => { screen = value; },
|
|
57
|
+
screen: () => screen,
|
|
58
|
+
plugin: env => {
|
|
59
|
+
const environment = env ?? defaultLoginEnv();
|
|
60
|
+
const plugin = plugins.find(candidate => candidate.match?.(environment, service.ctx) ?? true);
|
|
61
|
+
if (plugin == null) {
|
|
62
|
+
throw new Error('login: no plugin matches the current environment');
|
|
63
|
+
}
|
|
64
|
+
return plugin;
|
|
65
|
+
},
|
|
66
|
+
env: () => defaultLoginEnv(),
|
|
67
|
+
enter: () => {
|
|
68
|
+
const env = defaultLoginEnv();
|
|
69
|
+
service.plugin(env).enter?.(ctx(), env);
|
|
70
|
+
},
|
|
71
|
+
begin: request => {
|
|
72
|
+
const env = defaultLoginEnv();
|
|
73
|
+
// Synchronously, before anything can open a window: a precondition that refuses leaves the
|
|
74
|
+
// user exactly where `Gesture` describes — unable to proceed until they act again.
|
|
75
|
+
for (const precondition of preconditions) {
|
|
76
|
+
if (!precondition.check(ctx(), request, env)) {
|
|
77
|
+
return Promise.resolve(LoginOutcome.Gesture);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return service.plugin(env).begin(ctx(), request, env);
|
|
81
|
+
},
|
|
82
|
+
authorize: async (url) => {
|
|
83
|
+
const env = defaultLoginEnv();
|
|
84
|
+
return await service.plugin(env).authorize(ctx(), url, env);
|
|
85
|
+
},
|
|
86
|
+
complete: async (token) => {
|
|
87
|
+
const env = defaultLoginEnv();
|
|
88
|
+
return await service.plugin(env).complete(ctx(), token, env);
|
|
89
|
+
},
|
|
90
|
+
resume: async (token) => {
|
|
91
|
+
const env = defaultLoginEnv();
|
|
92
|
+
// Absent means "keep it and carry on" — which is what an ordinary tab has always done, and
|
|
93
|
+
// is why the redirect plugin implements nothing here.
|
|
94
|
+
return await service.plugin(env).resume?.(ctx(), token, env) ?? LoginOutcome.Passed;
|
|
95
|
+
},
|
|
96
|
+
logout: request => {
|
|
97
|
+
const env = defaultLoginEnv();
|
|
98
|
+
const plugin = service.plugin(env);
|
|
99
|
+
if (plugin.logout == null) {
|
|
100
|
+
// A plugin with no logout mechanic still has to end the session it started.
|
|
101
|
+
return revokeToken(ctx()).then(async () => {
|
|
102
|
+
await request.navigate?.();
|
|
103
|
+
return LoginOutcome.Passed;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return plugin.logout(ctx(), request, env);
|
|
107
|
+
},
|
|
108
|
+
logoutComplete: async () => {
|
|
109
|
+
const env = defaultLoginEnv();
|
|
110
|
+
return await service.plugin(env).logoutComplete?.(ctx(), env) ?? LoginOutcome.Passed;
|
|
111
|
+
},
|
|
112
|
+
adopt: async (token) => { await adoptToken(ctx(), token); },
|
|
113
|
+
revoke: async () => { await revokeToken(ctx()); },
|
|
114
|
+
});
|
|
115
|
+
return service;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Idempotently obtain the login service on a context, registering an empty host if none exists
|
|
119
|
+
* yet. Plugin packages call this before `registerPlugin`.
|
|
120
|
+
*/
|
|
121
|
+
export const ensureLoginService = (ctx) => {
|
|
122
|
+
if (ctx.hasService(DEFAULT_ALIAS)) {
|
|
123
|
+
return ctx.service(DEFAULT_ALIAS);
|
|
124
|
+
}
|
|
125
|
+
const service = makeLoginService();
|
|
126
|
+
ctx.registerService(service);
|
|
127
|
+
return service;
|
|
128
|
+
};
|
|
129
|
+
/** Register the host and expose it as `context.login()`. */
|
|
130
|
+
export const appendLogin = (ctx) => {
|
|
131
|
+
ensureLoginService(ctx);
|
|
132
|
+
const contextual = ctx;
|
|
133
|
+
contextual.login = () => ctx.service(DEFAULT_ALIAS);
|
|
134
|
+
return contextual;
|
|
135
|
+
};
|
|
136
|
+
export { LoginOutcome };
|
|
137
|
+
//# sourceMappingURL=service.js.map
|