@owlmeans/web-client 0.1.18-rc.6 → 0.1.18-rc.9
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/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/web-client/SKILL.md +45 -10
- package/build/context.d.ts.map +1 -1
- package/build/context.js +2 -0
- package/build/context.js.map +1 -1
- package/build/exports.d.ts +1 -0
- package/build/exports.d.ts.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/login/consts.d.ts +11 -0
- package/build/login/consts.d.ts.map +1 -0
- package/build/login/consts.js +11 -0
- package/build/login/consts.js.map +1 -0
- package/build/login/index.d.ts +5 -0
- package/build/login/index.d.ts.map +1 -0
- package/build/login/index.js +5 -0
- package/build/login/index.js.map +1 -0
- package/build/login/redirect.d.ts +9 -0
- package/build/login/redirect.d.ts.map +1 -0
- package/build/login/redirect.js +31 -0
- package/build/login/redirect.js.map +1 -0
- package/build/login/service.d.ts +10 -0
- package/build/login/service.d.ts.map +1 -0
- package/build/login/service.js +17 -0
- package/build/login/service.js.map +1 -0
- package/build/login/surrogate.d.ts +21 -0
- package/build/login/surrogate.d.ts.map +1 -0
- package/build/login/surrogate.js +105 -0
- package/build/login/surrogate.js.map +1 -0
- package/build/types.d.ts +2 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/context.ts +2 -0
- package/src/exports.ts +1 -0
- package/src/index.ts +1 -0
- package/src/login/consts.ts +12 -0
- package/src/login/index.ts +4 -0
- package/src/login/redirect.ts +38 -0
- package/src/login/service.ts +19 -0
- package/src/login/surrogate.ts +123 -0
- package/src/types.ts +2 -1
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/web-client",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-08-
|
|
4
|
+
"version": "0.1.18-rc.9",
|
|
5
|
+
"generatedAt": "2026-08-24T21:14:00.125Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -8,19 +8,21 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/web-client
|
|
9
9
|
|
|
10
10
|
**Layer:** Web (React)
|
|
11
|
-
**Install:** `"@owlmeans/web-client": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/web-client": "^0.1.18-rc.9"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
## Key Exports
|
|
14
14
|
|
|
15
15
|
| Export | Description |
|
|
16
16
|
|--------|-------------|
|
|
17
17
|
| `renderApp<C, T>(context)` | Mount the React app (routing via the active router plugin — OwlMeans by default) |
|
|
18
|
-
| `elevate(
|
|
18
|
+
| `elevate(entrypoints, alias, handler)` | Attach a React component (or `handler(Component)`) to an entrypoint |
|
|
19
19
|
| `handler(Component)` | Wrap a React component as an entrypoint handler |
|
|
20
|
-
| `context.registerEntrypoints(
|
|
20
|
+
| `context.registerEntrypoints(entrypoints)` | Register the full entrypoint list on the context |
|
|
21
21
|
| `context.serviceRoute(alias, isDefault?)` | Mark a service's routing root |
|
|
22
|
+
| `makeContext(cfg)` | Build the browser context — auth, web db, client resource, router and login are all appended here |
|
|
23
|
+
| `appendWebLogin(context)` | Register the login host plus the redirect and surrogate-window plugins |
|
|
22
24
|
| `router`, `components`, `service`, `i18n`, `helpers`, `errors` | Web-specific helpers |
|
|
23
|
-
| Constants | Default aliases (BASE, HOME) |
|
|
25
|
+
| Constants | Default aliases (BASE, HOME), `REDIRECT_LOGIN`, `SURROGATE_LOGIN` |
|
|
24
26
|
|
|
25
27
|
## Usage
|
|
26
28
|
|
|
@@ -29,31 +31,64 @@ user-invocable: false
|
|
|
29
31
|
// index.tsx
|
|
30
32
|
import { renderApp } from '@owlmeans/web-client'
|
|
31
33
|
import { makeContext } from './context.js'
|
|
32
|
-
import {
|
|
34
|
+
import { appEntrypoints } from './entrypoints.js'
|
|
33
35
|
import { MANAGER, MANAGER_API } from 'my-common'
|
|
34
36
|
import config from './config.js'
|
|
35
37
|
|
|
36
38
|
const context = makeContext(config)
|
|
37
|
-
context.registerEntrypoints(
|
|
39
|
+
context.registerEntrypoints(appEntrypoints)
|
|
38
40
|
context.serviceRoute(MANAGER, true)
|
|
39
41
|
context.serviceRoute(MANAGER_API, true)
|
|
40
42
|
renderApp<Config, Context>(context)
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
###
|
|
45
|
+
### Entrypoint elevation with React components
|
|
44
46
|
```typescript
|
|
45
|
-
import { elevate, route, frontend, handler, entrypoint
|
|
47
|
+
import { elevate, route, frontend, handler, entrypoint } from '@owlmeans/web-client'
|
|
46
48
|
import { HomeScreen, ProjectDashboardScreen } from './screens/index.js'
|
|
47
49
|
|
|
48
|
-
elevate(
|
|
50
|
+
elevate(entrypoints, manager.front.project.dashboard, handler(ProjectDashboardScreen))
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
entrypoints.push(
|
|
51
53
|
entrypoint(route(HOME, '/', frontend({ default: true, parent: BASE })), handler(HomeScreen))
|
|
52
54
|
)
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
A **backend** alias the client calls needs a bare `elevate(entrypoints, alias)` — no handler. That
|
|
58
|
+
makes `context.entrypoint<ClientEntrypoint<T>>(alias).call({ params, body, query })` work; the auth
|
|
59
|
+
header is attached automatically for guarded entrypoints.
|
|
60
|
+
|
|
61
|
+
### Login comes with the context
|
|
62
|
+
|
|
63
|
+
`makeContext` calls `appendWebLogin`, which registers the login host (`@owlmeans/client-auth/login`)
|
|
64
|
+
and both browser plugins on it — redirect (`REDIRECT_LOGIN`, priority 0, applies always) and
|
|
65
|
+
surrogate window (`SURROGATE_LOGIN`, priority 100, applies in a frame or in the surrogate itself).
|
|
66
|
+
So every web app — and everything built on `web-panel` or `mui-panel` — has working sign-in,
|
|
67
|
+
including from an embedded app, with nothing registered by hand:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { useLogin, useLogout } from '@owlmeans/client-auth/login'
|
|
71
|
+
|
|
72
|
+
const [, onLogIn] = useLogin() // one control, one handler, no environment checks
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
An app that needs a different mechanic registers its own plugin at a higher priority after building
|
|
76
|
+
the base context (`context.login().registerPlugin(...)`); it never replaces these. The contract, the
|
|
77
|
+
cascade and the invariants are the `login-plugins` skill.
|
|
78
|
+
|
|
79
|
+
### Navigation
|
|
80
|
+
```typescript
|
|
81
|
+
import { useNavigate } from '@owlmeans/client' // not re-exported by web-client
|
|
82
|
+
import { useStoreList, useStoreModel } from '@owlmeans/client' // client state — also not re-exported
|
|
83
|
+
|
|
84
|
+
const nav = useNavigate()
|
|
85
|
+
<Button onClick={nav.press(manager.front.project.dashboard, { params: { projectId } })}>Open</Button>
|
|
86
|
+
await nav.go(alias, { params }) // programmatic; nav.back() to go back
|
|
87
|
+
```
|
|
88
|
+
|
|
55
89
|
## Depends On
|
|
56
90
|
|
|
57
91
|
- `@owlmeans/client`, `@owlmeans/web-router` (default OwlMeans browser routing), `@owlmeans/web-panel` (typically), `@owlmeans/client-i18n`
|
|
92
|
+
- `@owlmeans/client-auth` — `AUTH_RESOURCE` and the `./login` host the browser plugins register on
|
|
58
93
|
- `@owlmeans/entrypoint`, `@owlmeans/route`
|
|
59
94
|
- `react`, `react-dom` (peer). No longer depends on `react-router` — opt into it with `@owlmeans/web-router-react-router` (`appendReactRouter`). The former `provide` export is a deprecated `undefined`.
|
package/build/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAUlD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,OAC7F,CAAC,KACL,CAaF,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,QACnF,CAAA"}
|
package/build/context.js
CHANGED
|
@@ -6,6 +6,7 @@ import { useContext as useCtx } from '@owlmeans/client';
|
|
|
6
6
|
import { extractPrimaryHost } from './helper.js';
|
|
7
7
|
import { appendWebAuthService } from './service.js';
|
|
8
8
|
import { appendWebRouter } from '@owlmeans/web-router';
|
|
9
|
+
import { appendWebLogin } from './login/service.js';
|
|
9
10
|
export const makeContext = (cfg) => {
|
|
10
11
|
const context = makeClientContext(cfg);
|
|
11
12
|
extractPrimaryHost(context);
|
|
@@ -13,6 +14,7 @@ export const makeContext = (cfg) => {
|
|
|
13
14
|
appendWebDbService(context);
|
|
14
15
|
appendClientResource(context, AUTH_RESOURCE);
|
|
15
16
|
appendWebRouter(context);
|
|
17
|
+
appendWebLogin(context);
|
|
16
18
|
context.makeContext = makeContext;
|
|
17
19
|
return context;
|
|
18
20
|
};
|
package/build/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,UAAU,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,UAAU,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEnD,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,GAAM,EACH,EAAE;IACL,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAM,CAAA;IAC3C,kBAAkB,CAAO,OAAO,CAAC,CAAA;IAEjC,oBAAoB,CAAO,OAAO,CAAC,CAAA;IACnC,kBAAkB,CAAO,OAAO,CAAC,CAAA;IACjC,oBAAoB,CAAO,OAAO,EAAE,aAAa,CAAC,CAAA;IAClD,eAAe,CAAO,OAAO,CAAC,CAAA;IAC9B,cAAc,CAAO,OAAO,CAAC,CAAA;IAE7B,OAAO,CAAC,WAAW,GAAG,WAAyC,CAAA;IAE/D,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,GAA4E,EAAE,CACtG,MAAM,EAAO,CAAA"}
|
package/build/exports.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { service } from '@owlmeans/config';
|
|
|
4
4
|
export { AppType, HOME, ROOT, BASE, GUEST } from '@owlmeans/context';
|
|
5
5
|
export { entrypoint, elevate, provideRequest, stab } from '@owlmeans/client-entrypoint';
|
|
6
6
|
export { route, frontend } from '@owlmeans/route';
|
|
7
|
+
export type { ClientEntrypoint } from '@owlmeans/client-entrypoint';
|
|
7
8
|
export type { ClientEntrypoint as Module } from '@owlmeans/client-entrypoint';
|
|
8
9
|
export type { ClientRoute as Route } from '@owlmeans/client-route';
|
|
9
10
|
//# sourceMappingURL=exports.d.ts.map
|
package/build/exports.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AACvF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAC7E,YAAY,EAAE,WAAW,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAA"}
|
|
1
|
+
{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AACvF,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAC7E,YAAY,EAAE,WAAW,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAA"}
|
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA"}
|
package/build/index.js
CHANGED
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The ordinary flow: leave for the provider and come back. */
|
|
2
|
+
export declare const REDIRECT_LOGIN = "owlmeans-redirect-login";
|
|
3
|
+
/** The flow that runs one window up, for a document that cannot redirect where it is. */
|
|
4
|
+
export declare const SURROGATE_LOGIN = "owlmeans-surrogate-login";
|
|
5
|
+
/**
|
|
6
|
+
* Registered above the default so it wins the cascade wherever it applies. It applies narrowly —
|
|
7
|
+
* only in a frame, or in the surrogate window itself — so the default still serves every
|
|
8
|
+
* ordinary tab.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SURROGATE_LOGIN_PRIORITY = 100;
|
|
11
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../../src/login/consts.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,eAAO,MAAM,cAAc,4BAA4B,CAAA;AAEvD,yFAAyF;AACzF,eAAO,MAAM,eAAe,6BAA6B,CAAA;AAEzD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The ordinary flow: leave for the provider and come back. */
|
|
2
|
+
export const REDIRECT_LOGIN = 'owlmeans-redirect-login';
|
|
3
|
+
/** The flow that runs one window up, for a document that cannot redirect where it is. */
|
|
4
|
+
export const SURROGATE_LOGIN = 'owlmeans-surrogate-login';
|
|
5
|
+
/**
|
|
6
|
+
* Registered above the default so it wins the cascade wherever it applies. It applies narrowly —
|
|
7
|
+
* only in a frame, or in the surrogate window itself — so the default still serves every
|
|
8
|
+
* ordinary tab.
|
|
9
|
+
*/
|
|
10
|
+
export const SURROGATE_LOGIN_PRIORITY = 100;
|
|
11
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../../src/login/consts.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,CAAC,MAAM,cAAc,GAAG,yBAAyB,CAAA;AAEvD,yFAAyF;AACzF,MAAM,CAAC,MAAM,eAAe,GAAG,0BAA0B,CAAA;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/login/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LoginPlugin } from '@owlmeans/client-auth/login';
|
|
2
|
+
/**
|
|
3
|
+
* The ordinary login flow: this document leaves for the identity provider and comes back.
|
|
4
|
+
*
|
|
5
|
+
* The default plugin, matching everything, so any environment that does not need special handling
|
|
6
|
+
* behaves exactly as it always did.
|
|
7
|
+
*/
|
|
8
|
+
export declare const makeRedirectLoginPlugin: () => LoginPlugin;
|
|
9
|
+
//# sourceMappingURL=redirect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/login/redirect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAG9D;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAAO,WA2BzC,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LoginOutcome } from '@owlmeans/client-auth/login';
|
|
2
|
+
import { REDIRECT_LOGIN } from './consts.js';
|
|
3
|
+
/**
|
|
4
|
+
* The ordinary login flow: this document leaves for the identity provider and comes back.
|
|
5
|
+
*
|
|
6
|
+
* The default plugin, matching everything, so any environment that does not need special handling
|
|
7
|
+
* behaves exactly as it always did.
|
|
8
|
+
*/
|
|
9
|
+
export const makeRedirectLoginPlugin = () => ({
|
|
10
|
+
alias: REDIRECT_LOGIN,
|
|
11
|
+
priority: 0,
|
|
12
|
+
mode: 'redirect',
|
|
13
|
+
match: () => true,
|
|
14
|
+
begin: async (_ctx, request) => {
|
|
15
|
+
// Prefer the in-app continuation the caller handed over: a client-side navigation to the
|
|
16
|
+
// dispatcher keeps the app mounted, where a full page load would tear it down and rebuild it.
|
|
17
|
+
if (request.navigate != null) {
|
|
18
|
+
await request.navigate();
|
|
19
|
+
return LoginOutcome.Handled;
|
|
20
|
+
}
|
|
21
|
+
window.location.href = request.url;
|
|
22
|
+
return LoginOutcome.Redirected;
|
|
23
|
+
},
|
|
24
|
+
authorize: async (_ctx, url) => {
|
|
25
|
+
window.location.href = url;
|
|
26
|
+
return LoginOutcome.Redirected;
|
|
27
|
+
},
|
|
28
|
+
// Nothing to hand anywhere — the token was issued in the document that will use it.
|
|
29
|
+
complete: async () => LoginOutcome.Passed,
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=redirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirect.js","sourceRoot":"","sources":["../../src/login/redirect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE1D,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAgB,EAAE,CAAC,CAAC;IACzD,KAAK,EAAE,cAAc;IACrB,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI;IAEjB,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QAC7B,yFAAyF;QACzF,8FAA8F;QAC9F,IAAI,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;YAC7B,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAA;YAExB,OAAO,YAAY,CAAC,OAAO,CAAA;QAC7B,CAAC;QACD,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,CAAA;QAElC,OAAO,YAAY,CAAC,UAAU,CAAA;IAChC,CAAC;IAED,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QAC7B,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAA;QAE1B,OAAO,YAAY,CAAC,UAAU,CAAA;IAChC,CAAC;IAED,oFAAoF;IACpF,QAAQ,EAAE,KAAK,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM;CAC1C,CAAC,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BasicConfig, BasicContext } from '@owlmeans/context';
|
|
2
|
+
/**
|
|
3
|
+
* Register the login host and the two browser flows on a context.
|
|
4
|
+
*
|
|
5
|
+
* Called by `makeContext`, so every web app has working login — including a framed one — without
|
|
6
|
+
* registering anything. An app that needs a different mechanic registers its own plugin at a
|
|
7
|
+
* higher priority; it does not replace this.
|
|
8
|
+
*/
|
|
9
|
+
export declare const appendWebLogin: <C extends BasicConfig, T extends BasicContext<C>>(ctx: T) => T;
|
|
10
|
+
//# 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;AAIlE;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,KAAG,CAMzF,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { appendLogin } from '@owlmeans/client-auth/login';
|
|
2
|
+
import { makeRedirectLoginPlugin } from './redirect.js';
|
|
3
|
+
import { makeSurrogateLoginPlugin } from './surrogate.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register the login host and the two browser flows on a context.
|
|
6
|
+
*
|
|
7
|
+
* Called by `makeContext`, so every web app has working login — including a framed one — without
|
|
8
|
+
* registering anything. An app that needs a different mechanic registers its own plugin at a
|
|
9
|
+
* higher priority; it does not replace this.
|
|
10
|
+
*/
|
|
11
|
+
export const appendWebLogin = (ctx) => {
|
|
12
|
+
const service = appendLogin(ctx).login();
|
|
13
|
+
service.registerPlugin(makeRedirectLoginPlugin());
|
|
14
|
+
service.registerPlugin(makeSurrogateLoginPlugin());
|
|
15
|
+
return ctx;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/login/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAEzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAmD,GAAM,EAAK,EAAE;IAC5F,MAAM,OAAO,GAAG,WAAW,CAAO,GAAG,CAAC,CAAC,KAAK,EAAE,CAAA;IAC9C,OAAO,CAAC,cAAc,CAAC,uBAAuB,EAAE,CAAC,CAAA;IACjD,OAAO,CAAC,cAAc,CAAC,wBAAwB,EAAE,CAAC,CAAA;IAElD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LoginPlugin } from '@owlmeans/client-auth/login';
|
|
2
|
+
/**
|
|
3
|
+
* Login for a document that cannot complete the flow where it is.
|
|
4
|
+
*
|
|
5
|
+
* An app embedded in a frame cannot redirect to an identity provider: the provider refuses to be
|
|
6
|
+
* embedded (`frame-ancestors`), and its session cookies are third-party there. So the flow runs
|
|
7
|
+
* one window up, in a surrogate window that is top-level and first-party on this application's own
|
|
8
|
+
* origin — where the very same flow completes normally — and the token is passed back to the
|
|
9
|
+
* document that asked for it.
|
|
10
|
+
*
|
|
11
|
+
* This plugin is deliberately not OIDC-specific. The mechanic is "run the login route one window
|
|
12
|
+
* up and hand the token back", and the adoption is the plain auth service, so any relying party
|
|
13
|
+
* that renders a login route gets it by registering nothing.
|
|
14
|
+
*
|
|
15
|
+
* `match` covers BOTH halves of the lifecycle — the framed opener and the surrogate itself, where
|
|
16
|
+
* `embedded` is false. Inside the surrogate the redirect plugin must not win: that window IS
|
|
17
|
+
* top-level, so it authorizes by an ordinary redirect and differs only in what it does with the
|
|
18
|
+
* token at the end.
|
|
19
|
+
*/
|
|
20
|
+
export declare const makeSurrogateLoginPlugin: () => LoginPlugin;
|
|
21
|
+
//# sourceMappingURL=surrogate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surrogate.d.ts","sourceRoot":"","sources":["../../src/login/surrogate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAG5E;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,wBAAwB,QAAO,WAiG1C,CAAA"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { adoptToken, clearSurrogate, markSurrogate, LoginOutcome, LOGIN_SURROGATE_FEATURES, LOGIN_SURROGATE_NAME, LOGIN_TOKEN_MESSAGE, LOGIN_WATCH_INTERVAL, } from '@owlmeans/client-auth/login';
|
|
2
|
+
import { SURROGATE_LOGIN, SURROGATE_LOGIN_PRIORITY } from './consts.js';
|
|
3
|
+
/**
|
|
4
|
+
* Login for a document that cannot complete the flow where it is.
|
|
5
|
+
*
|
|
6
|
+
* An app embedded in a frame cannot redirect to an identity provider: the provider refuses to be
|
|
7
|
+
* embedded (`frame-ancestors`), and its session cookies are third-party there. So the flow runs
|
|
8
|
+
* one window up, in a surrogate window that is top-level and first-party on this application's own
|
|
9
|
+
* origin — where the very same flow completes normally — and the token is passed back to the
|
|
10
|
+
* document that asked for it.
|
|
11
|
+
*
|
|
12
|
+
* This plugin is deliberately not OIDC-specific. The mechanic is "run the login route one window
|
|
13
|
+
* up and hand the token back", and the adoption is the plain auth service, so any relying party
|
|
14
|
+
* that renders a login route gets it by registering nothing.
|
|
15
|
+
*
|
|
16
|
+
* `match` covers BOTH halves of the lifecycle — the framed opener and the surrogate itself, where
|
|
17
|
+
* `embedded` is false. Inside the surrogate the redirect plugin must not win: that window IS
|
|
18
|
+
* top-level, so it authorizes by an ordinary redirect and differs only in what it does with the
|
|
19
|
+
* token at the end.
|
|
20
|
+
*/
|
|
21
|
+
export const makeSurrogateLoginPlugin = () => ({
|
|
22
|
+
alias: SURROGATE_LOGIN,
|
|
23
|
+
priority: SURROGATE_LOGIN_PRIORITY,
|
|
24
|
+
mode: 'surrogate',
|
|
25
|
+
match: env => env.hasWindow && (env.embedded || env.surrogate),
|
|
26
|
+
enter: () => { markSurrogate(); },
|
|
27
|
+
begin: (ctx, request, env) => {
|
|
28
|
+
// Already one window up — nothing to open, just run the flow.
|
|
29
|
+
if (env.surrogate) {
|
|
30
|
+
window.location.href = request.url;
|
|
31
|
+
return Promise.resolve(LoginOutcome.Redirected);
|
|
32
|
+
}
|
|
33
|
+
// Opened FIRST and synchronously: `window.open` escapes the popup blocker only while the user
|
|
34
|
+
// gesture is still being handled, so nothing may be awaited before this line. That is why this
|
|
35
|
+
// function is not `async`.
|
|
36
|
+
const surrogate = window.open(request.url, LOGIN_SURROGATE_NAME, LOGIN_SURROGATE_FEATURES);
|
|
37
|
+
if (surrogate == null) {
|
|
38
|
+
return Promise.resolve(LoginOutcome.Failed);
|
|
39
|
+
}
|
|
40
|
+
return new Promise(resolve => {
|
|
41
|
+
let settled = false;
|
|
42
|
+
let watch;
|
|
43
|
+
const finish = (result) => {
|
|
44
|
+
if (settled) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
settled = true;
|
|
48
|
+
window.removeEventListener('message', onMessage);
|
|
49
|
+
if (watch != null) {
|
|
50
|
+
clearInterval(watch);
|
|
51
|
+
}
|
|
52
|
+
resolve(result);
|
|
53
|
+
};
|
|
54
|
+
function onMessage(event) {
|
|
55
|
+
if (event.origin !== window.location.origin) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const data = event.data;
|
|
59
|
+
if (data?.type !== LOGIN_TOKEN_MESSAGE || data.token == null || data.token === '') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
void adoptToken(ctx, data.token)
|
|
63
|
+
.then(() => finish(LoginOutcome.Handled))
|
|
64
|
+
.catch(() => finish(LoginOutcome.Failed));
|
|
65
|
+
}
|
|
66
|
+
window.addEventListener('message', onMessage);
|
|
67
|
+
// A window the user simply closes announces nothing, so the only way to stop waiting on it
|
|
68
|
+
// is to watch for it going away.
|
|
69
|
+
watch = setInterval(() => {
|
|
70
|
+
if (surrogate.closed) {
|
|
71
|
+
finish(LoginOutcome.Failed);
|
|
72
|
+
}
|
|
73
|
+
}, LOGIN_WATCH_INTERVAL);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
authorize: async (_ctx, url, env) => {
|
|
77
|
+
// The surrogate is top-level, so it redirects like any ordinary tab.
|
|
78
|
+
if (env.surrogate) {
|
|
79
|
+
window.location.href = url;
|
|
80
|
+
return LoginOutcome.Redirected;
|
|
81
|
+
}
|
|
82
|
+
// Framed, and not yet one window up. A redirect from here would land in the frame and be
|
|
83
|
+
// refused, and the window that would fix it cannot be opened without a user gesture — so the
|
|
84
|
+
// caller is told to render a control rather than to navigate.
|
|
85
|
+
return LoginOutcome.Gesture;
|
|
86
|
+
},
|
|
87
|
+
complete: async (_ctx, token, env) => {
|
|
88
|
+
if (!env.surrogate) {
|
|
89
|
+
// This document started the flow and finished it in place — it already has what it needs.
|
|
90
|
+
return LoginOutcome.Passed;
|
|
91
|
+
}
|
|
92
|
+
if (!env.hasOpener) {
|
|
93
|
+
// Authenticated with nowhere to send it. `Cross-Origin-Opener-Policy: same-origin` from the
|
|
94
|
+
// provider severs `window.opener` permanently, so this is not a timing problem to retry.
|
|
95
|
+
return LoginOutcome.Orphaned;
|
|
96
|
+
}
|
|
97
|
+
clearSurrogate();
|
|
98
|
+
// Both windows are the same origin (the surrogate is this application's own login route), so
|
|
99
|
+
// the origin is pinned rather than passed as `*` — the message carries a bearer token.
|
|
100
|
+
window.opener.postMessage({ type: LOGIN_TOKEN_MESSAGE, token }, window.location.origin);
|
|
101
|
+
window.close();
|
|
102
|
+
return LoginOutcome.Handled;
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=surrogate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surrogate.js","sourceRoot":"","sources":["../../src/login/surrogate.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuthServiceAppend } from '@owlmeans/client-auth';
|
|
2
|
+
import type { LoginServiceAppend } from '@owlmeans/client-auth/login';
|
|
2
3
|
import type { ClientConfig } from '@owlmeans/client-context';
|
|
3
4
|
import type { ClientContext } from '@owlmeans/client';
|
|
4
5
|
export interface RenderOptions {
|
|
@@ -9,6 +10,6 @@ export interface RenderOptions {
|
|
|
9
10
|
}
|
|
10
11
|
export interface AppConfig extends ClientConfig {
|
|
11
12
|
}
|
|
12
|
-
export interface AppContext<C extends AppConfig = AppConfig> extends ClientContext<C>, AuthServiceAppend {
|
|
13
|
+
export interface AppContext<C extends AppConfig = AppConfig> extends ClientContext<C>, AuthServiceAppend, LoginServiceAppend {
|
|
13
14
|
}
|
|
14
15
|
//# sourceMappingURL=types.d.ts.map
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAErD,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,SAAU,SAAQ,YAAY;CAC9C;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC,EACnF,iBAAiB;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAErD,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,SAAU,SAAQ,YAAY;CAC9C;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC,EACnF,iBAAiB,EAAE,kBAAkB;CACtC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-client",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -22,21 +22,21 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@owlmeans/auth": "^0.1.18-rc.6",
|
|
25
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/client": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/client-auth": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/client-context": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/client-i18n": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/client-entrypoint": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/client-resource": "^0.1.18-rc.
|
|
32
|
-
"@owlmeans/client-route": "^0.1.18-rc.
|
|
33
|
-
"@owlmeans/config": "^0.1.18-rc.
|
|
25
|
+
"@owlmeans/auth-common": "^0.1.18-rc.7",
|
|
26
|
+
"@owlmeans/client": "^0.1.18-rc.8",
|
|
27
|
+
"@owlmeans/client-auth": "^0.1.18-rc.9",
|
|
28
|
+
"@owlmeans/client-context": "^0.1.18-rc.7",
|
|
29
|
+
"@owlmeans/client-i18n": "^0.1.18-rc.7",
|
|
30
|
+
"@owlmeans/client-entrypoint": "^0.1.18-rc.7",
|
|
31
|
+
"@owlmeans/client-resource": "^0.1.18-rc.7",
|
|
32
|
+
"@owlmeans/client-route": "^0.1.18-rc.7",
|
|
33
|
+
"@owlmeans/config": "^0.1.18-rc.7",
|
|
34
34
|
"@owlmeans/context": "^0.1.18-rc.6",
|
|
35
35
|
"@owlmeans/error": "^0.1.18-rc.6",
|
|
36
36
|
"@owlmeans/i18n": "^0.1.18-rc.6",
|
|
37
37
|
"@owlmeans/route": "^0.1.18-rc.6",
|
|
38
|
-
"@owlmeans/web-db": "^0.1.18-rc.
|
|
39
|
-
"@owlmeans/web-router": "^0.1.18-rc.
|
|
38
|
+
"@owlmeans/web-db": "^0.1.18-rc.7",
|
|
39
|
+
"@owlmeans/web-router": "^0.1.18-rc.7",
|
|
40
40
|
"react-dropzone": "^14.3.5"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
package/src/context.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { useContext as useCtx } from '@owlmeans/client'
|
|
|
8
8
|
import { extractPrimaryHost } from './helper.js'
|
|
9
9
|
import { appendWebAuthService } from './service.js'
|
|
10
10
|
import { appendWebRouter } from '@owlmeans/web-router'
|
|
11
|
+
import { appendWebLogin } from './login/service.js'
|
|
11
12
|
|
|
12
13
|
export const makeContext = <C extends AppConfig = AppConfig, T extends AppContext<C> = AppContext<C>>(
|
|
13
14
|
cfg: C
|
|
@@ -19,6 +20,7 @@ export const makeContext = <C extends AppConfig = AppConfig, T extends AppContex
|
|
|
19
20
|
appendWebDbService<C, T>(context)
|
|
20
21
|
appendClientResource<C, T>(context, AUTH_RESOURCE)
|
|
21
22
|
appendWebRouter<C, T>(context)
|
|
23
|
+
appendWebLogin<C, T>(context)
|
|
22
24
|
|
|
23
25
|
context.makeContext = makeContext as typeof context.makeContext
|
|
24
26
|
|
package/src/exports.ts
CHANGED
|
@@ -5,5 +5,6 @@ export { service } from '@owlmeans/config'
|
|
|
5
5
|
export { AppType, HOME, ROOT, BASE, GUEST } from '@owlmeans/context'
|
|
6
6
|
export { entrypoint, elevate, provideRequest, stab } from '@owlmeans/client-entrypoint'
|
|
7
7
|
export { route, frontend } from '@owlmeans/route'
|
|
8
|
+
export type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
8
9
|
export type { ClientEntrypoint as Module } from '@owlmeans/client-entrypoint'
|
|
9
10
|
export type { ClientRoute as Route } from '@owlmeans/client-route'
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The ordinary flow: leave for the provider and come back. */
|
|
2
|
+
export const REDIRECT_LOGIN = 'owlmeans-redirect-login'
|
|
3
|
+
|
|
4
|
+
/** The flow that runs one window up, for a document that cannot redirect where it is. */
|
|
5
|
+
export const SURROGATE_LOGIN = 'owlmeans-surrogate-login'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registered above the default so it wins the cascade wherever it applies. It applies narrowly —
|
|
9
|
+
* only in a frame, or in the surrogate window itself — so the default still serves every
|
|
10
|
+
* ordinary tab.
|
|
11
|
+
*/
|
|
12
|
+
export const SURROGATE_LOGIN_PRIORITY = 100
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { LoginOutcome } from '@owlmeans/client-auth/login'
|
|
2
|
+
import type { LoginPlugin } from '@owlmeans/client-auth/login'
|
|
3
|
+
import { REDIRECT_LOGIN } from './consts.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The ordinary login flow: this document leaves for the identity provider and comes back.
|
|
7
|
+
*
|
|
8
|
+
* The default plugin, matching everything, so any environment that does not need special handling
|
|
9
|
+
* behaves exactly as it always did.
|
|
10
|
+
*/
|
|
11
|
+
export const makeRedirectLoginPlugin = (): LoginPlugin => ({
|
|
12
|
+
alias: REDIRECT_LOGIN,
|
|
13
|
+
priority: 0,
|
|
14
|
+
mode: 'redirect',
|
|
15
|
+
match: () => true,
|
|
16
|
+
|
|
17
|
+
begin: async (_ctx, request) => {
|
|
18
|
+
// Prefer the in-app continuation the caller handed over: a client-side navigation to the
|
|
19
|
+
// dispatcher keeps the app mounted, where a full page load would tear it down and rebuild it.
|
|
20
|
+
if (request.navigate != null) {
|
|
21
|
+
await request.navigate()
|
|
22
|
+
|
|
23
|
+
return LoginOutcome.Handled
|
|
24
|
+
}
|
|
25
|
+
window.location.href = request.url
|
|
26
|
+
|
|
27
|
+
return LoginOutcome.Redirected
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
authorize: async (_ctx, url) => {
|
|
31
|
+
window.location.href = url
|
|
32
|
+
|
|
33
|
+
return LoginOutcome.Redirected
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Nothing to hand anywhere — the token was issued in the document that will use it.
|
|
37
|
+
complete: async () => LoginOutcome.Passed,
|
|
38
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { appendLogin } from '@owlmeans/client-auth/login'
|
|
2
|
+
import type { BasicConfig, BasicContext } from '@owlmeans/context'
|
|
3
|
+
import { makeRedirectLoginPlugin } from './redirect.js'
|
|
4
|
+
import { makeSurrogateLoginPlugin } from './surrogate.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register the login host and the two browser flows on a context.
|
|
8
|
+
*
|
|
9
|
+
* Called by `makeContext`, so every web app has working login — including a framed one — without
|
|
10
|
+
* registering anything. An app that needs a different mechanic registers its own plugin at a
|
|
11
|
+
* higher priority; it does not replace this.
|
|
12
|
+
*/
|
|
13
|
+
export const appendWebLogin = <C extends BasicConfig, T extends BasicContext<C>>(ctx: T): T => {
|
|
14
|
+
const service = appendLogin<C, T>(ctx).login()
|
|
15
|
+
service.registerPlugin(makeRedirectLoginPlugin())
|
|
16
|
+
service.registerPlugin(makeSurrogateLoginPlugin())
|
|
17
|
+
|
|
18
|
+
return ctx
|
|
19
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adoptToken, clearSurrogate, markSurrogate, LoginOutcome,
|
|
3
|
+
LOGIN_SURROGATE_FEATURES, LOGIN_SURROGATE_NAME, LOGIN_TOKEN_MESSAGE, LOGIN_WATCH_INTERVAL,
|
|
4
|
+
} from '@owlmeans/client-auth/login'
|
|
5
|
+
import type { LoginContext, LoginPlugin } from '@owlmeans/client-auth/login'
|
|
6
|
+
import { SURROGATE_LOGIN, SURROGATE_LOGIN_PRIORITY } from './consts.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Login for a document that cannot complete the flow where it is.
|
|
10
|
+
*
|
|
11
|
+
* An app embedded in a frame cannot redirect to an identity provider: the provider refuses to be
|
|
12
|
+
* embedded (`frame-ancestors`), and its session cookies are third-party there. So the flow runs
|
|
13
|
+
* one window up, in a surrogate window that is top-level and first-party on this application's own
|
|
14
|
+
* origin — where the very same flow completes normally — and the token is passed back to the
|
|
15
|
+
* document that asked for it.
|
|
16
|
+
*
|
|
17
|
+
* This plugin is deliberately not OIDC-specific. The mechanic is "run the login route one window
|
|
18
|
+
* up and hand the token back", and the adoption is the plain auth service, so any relying party
|
|
19
|
+
* that renders a login route gets it by registering nothing.
|
|
20
|
+
*
|
|
21
|
+
* `match` covers BOTH halves of the lifecycle — the framed opener and the surrogate itself, where
|
|
22
|
+
* `embedded` is false. Inside the surrogate the redirect plugin must not win: that window IS
|
|
23
|
+
* top-level, so it authorizes by an ordinary redirect and differs only in what it does with the
|
|
24
|
+
* token at the end.
|
|
25
|
+
*/
|
|
26
|
+
export const makeSurrogateLoginPlugin = (): LoginPlugin => ({
|
|
27
|
+
alias: SURROGATE_LOGIN,
|
|
28
|
+
priority: SURROGATE_LOGIN_PRIORITY,
|
|
29
|
+
mode: 'surrogate',
|
|
30
|
+
match: env => env.hasWindow && (env.embedded || env.surrogate),
|
|
31
|
+
|
|
32
|
+
enter: () => { markSurrogate() },
|
|
33
|
+
|
|
34
|
+
begin: (ctx, request, env) => {
|
|
35
|
+
// Already one window up — nothing to open, just run the flow.
|
|
36
|
+
if (env.surrogate) {
|
|
37
|
+
window.location.href = request.url
|
|
38
|
+
|
|
39
|
+
return Promise.resolve(LoginOutcome.Redirected)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Opened FIRST and synchronously: `window.open` escapes the popup blocker only while the user
|
|
43
|
+
// gesture is still being handled, so nothing may be awaited before this line. That is why this
|
|
44
|
+
// function is not `async`.
|
|
45
|
+
const surrogate = window.open(request.url, LOGIN_SURROGATE_NAME, LOGIN_SURROGATE_FEATURES)
|
|
46
|
+
if (surrogate == null) {
|
|
47
|
+
return Promise.resolve(LoginOutcome.Failed)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new Promise<LoginOutcome>(resolve => {
|
|
51
|
+
let settled = false
|
|
52
|
+
let watch: ReturnType<typeof setInterval> | undefined
|
|
53
|
+
|
|
54
|
+
const finish = (result: LoginOutcome): void => {
|
|
55
|
+
if (settled) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
settled = true
|
|
59
|
+
window.removeEventListener('message', onMessage)
|
|
60
|
+
if (watch != null) {
|
|
61
|
+
clearInterval(watch)
|
|
62
|
+
}
|
|
63
|
+
resolve(result)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function onMessage(event: MessageEvent): void {
|
|
67
|
+
if (event.origin !== window.location.origin) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const data = event.data as { type?: string, token?: string } | null
|
|
71
|
+
if (data?.type !== LOGIN_TOKEN_MESSAGE || data.token == null || data.token === '') {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
void adoptToken(ctx as LoginContext, data.token)
|
|
75
|
+
.then(() => finish(LoginOutcome.Handled))
|
|
76
|
+
.catch(() => finish(LoginOutcome.Failed))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
window.addEventListener('message', onMessage)
|
|
80
|
+
// A window the user simply closes announces nothing, so the only way to stop waiting on it
|
|
81
|
+
// is to watch for it going away.
|
|
82
|
+
watch = setInterval(() => {
|
|
83
|
+
if (surrogate.closed) {
|
|
84
|
+
finish(LoginOutcome.Failed)
|
|
85
|
+
}
|
|
86
|
+
}, LOGIN_WATCH_INTERVAL)
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
authorize: async (_ctx, url, env) => {
|
|
91
|
+
// The surrogate is top-level, so it redirects like any ordinary tab.
|
|
92
|
+
if (env.surrogate) {
|
|
93
|
+
window.location.href = url
|
|
94
|
+
|
|
95
|
+
return LoginOutcome.Redirected
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Framed, and not yet one window up. A redirect from here would land in the frame and be
|
|
99
|
+
// refused, and the window that would fix it cannot be opened without a user gesture — so the
|
|
100
|
+
// caller is told to render a control rather than to navigate.
|
|
101
|
+
return LoginOutcome.Gesture
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
complete: async (_ctx, token, env) => {
|
|
105
|
+
if (!env.surrogate) {
|
|
106
|
+
// This document started the flow and finished it in place — it already has what it needs.
|
|
107
|
+
return LoginOutcome.Passed
|
|
108
|
+
}
|
|
109
|
+
if (!env.hasOpener) {
|
|
110
|
+
// Authenticated with nowhere to send it. `Cross-Origin-Opener-Policy: same-origin` from the
|
|
111
|
+
// provider severs `window.opener` permanently, so this is not a timing problem to retry.
|
|
112
|
+
return LoginOutcome.Orphaned
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
clearSurrogate()
|
|
116
|
+
// Both windows are the same origin (the surrogate is this application's own login route), so
|
|
117
|
+
// the origin is pinned rather than passed as `*` — the message carries a bearer token.
|
|
118
|
+
window.opener.postMessage({ type: LOGIN_TOKEN_MESSAGE, token }, window.location.origin)
|
|
119
|
+
window.close()
|
|
120
|
+
|
|
121
|
+
return LoginOutcome.Handled
|
|
122
|
+
},
|
|
123
|
+
})
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuthServiceAppend } from '@owlmeans/client-auth'
|
|
2
|
+
import type { LoginServiceAppend } from '@owlmeans/client-auth/login'
|
|
2
3
|
import type { ClientConfig } from '@owlmeans/client-context'
|
|
3
4
|
import type { ClientContext } from '@owlmeans/client'
|
|
4
5
|
|
|
@@ -13,5 +14,5 @@ export interface AppConfig extends ClientConfig {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface AppContext<C extends AppConfig = AppConfig> extends ClientContext<C>,
|
|
16
|
-
AuthServiceAppend {
|
|
17
|
+
AuthServiceAppend, LoginServiceAppend {
|
|
17
18
|
}
|