@wral/studio.mods.auth 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bitbucket-pipelines.yml +3 -3
- package/dist/auth.cjs.js +119 -205
- package/dist/auth.es.js +387 -606
- package/dist/lib.cjs.js +1 -1
- package/dist/lib.es.js +4 -6
- package/example-app.mjs +34 -0
- package/index.html +19 -72
- package/package.json +3 -2
- package/src/{components/forgot-password-form.mjs → forgot-password-form.mjs} +69 -69
- package/src/helper.mjs +5 -16
- package/src/index.mjs +108 -12
- package/src/layout.mjs +38 -0
- package/src/{components/login-form.mjs → login-form.mjs} +125 -122
- package/src/token.mjs +34 -30
- package/vellum/README.md +1 -0
- package/vellum/app-manager.mjs +126 -0
- package/vellum/example-app.mjs +34 -0
- package/vellum/index.mjs +26 -0
- package/vellum/layout.mjs +85 -0
- package/vellum/mod-setup.mjs +22 -0
- package/vellum/render.mjs +21 -0
- package/vellum/themes/default/index.css +89 -0
- package/vite.config.mjs +1 -1
- package/src/auth.mjs +0 -208
- package/src/auth.test.mjs +0 -97
- package/src/components/auth-app.mjs +0 -26
- package/src/config.mjs +0 -27
- package/src/login-layout.mjs +0 -32
- package/src/login.mjs +0 -20
- package/src/routes/change-password.mjs +0 -158
- package/src/routes/dashboard.mjs +0 -17
- package/src/routes/index.mjs +0 -15
- package/src/state.mjs +0 -61
- package/src/state.test.mjs +0 -58
- package/src/styles.mjs +0 -9
- package/src/utils.mjs +0 -3
- package/vellum-fixture.mjs +0 -86
package/dist/lib.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function o(e){return new Promise((t,n)=>{
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function o(e){return new Promise((t,n)=>{e!=null&&e.dispatchEvent||n(new Error("getToken must be called with an eventTarget or DOM element")),e.dispatchEvent(new CustomEvent("harness:action",{detail:{type:"auth:requestToken",detail:{callback:t}},bubbles:!0,composed:!0,cancelable:!0}))})}exports.getToken=o;
|
package/dist/lib.es.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
function
|
|
1
|
+
function o(e) {
|
|
2
2
|
return new Promise((t, n) => {
|
|
3
|
-
|
|
3
|
+
e != null && e.dispatchEvent || n(new Error("getToken must be called with an eventTarget or DOM element")), e.dispatchEvent(new CustomEvent("harness:action", {
|
|
4
4
|
detail: {
|
|
5
5
|
type: "auth:requestToken",
|
|
6
|
-
detail: {
|
|
7
|
-
callback: t
|
|
8
|
-
}
|
|
6
|
+
detail: { callback: t }
|
|
9
7
|
},
|
|
10
8
|
bubbles: !0,
|
|
11
9
|
composed: !0,
|
|
@@ -14,5 +12,5 @@ function a(e) {
|
|
|
14
12
|
});
|
|
15
13
|
}
|
|
16
14
|
export {
|
|
17
|
-
|
|
15
|
+
o as getToken
|
|
18
16
|
};
|
package/example-app.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
|
|
3
|
+
export async function init(toolkit, mod) {
|
|
4
|
+
|
|
5
|
+
let token = null;
|
|
6
|
+
|
|
7
|
+
const content = html`
|
|
8
|
+
<div slot="main" id="example-app">
|
|
9
|
+
<h1>Example</h1>
|
|
10
|
+
<p>This is an example app.</p>
|
|
11
|
+
<pre><code>${JSON.stringify(mod, null, 2)}</code></pre>
|
|
12
|
+
</div>
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
toolkit.dispatchAction({
|
|
16
|
+
type: 'app:register',
|
|
17
|
+
detail: {
|
|
18
|
+
id: 'example',
|
|
19
|
+
name: 'Example App',
|
|
20
|
+
content: () => content,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
toolkit.dispatchAction({
|
|
25
|
+
type: 'auth:requestToken',
|
|
26
|
+
detail: {
|
|
27
|
+
callback: (t) => {
|
|
28
|
+
token = t;
|
|
29
|
+
console.log("got a token!", token);
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
}
|
package/index.html
CHANGED
|
@@ -4,82 +4,29 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>
|
|
8
|
-
|
|
7
|
+
<title>Auth Mod Fixture App</title>
|
|
8
|
+
<link rel="stylesheet" href="./vellum/themes/default/index.css">
|
|
9
9
|
<meta name="robots" content="noindex, nofollow">
|
|
10
|
+
<script type="module" src="./vellum/index.mjs"></script>
|
|
10
11
|
</head>
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
16
|
-
html {
|
|
17
|
-
--spacing-sm: 0.5rem;
|
|
18
|
-
--spacing-md: 1rem;
|
|
19
|
-
--spacing-lg: 2rem;
|
|
20
|
-
--spacing-xl: 3rem;
|
|
21
|
-
|
|
22
|
-
--radius-sm: 5px;
|
|
23
|
-
--radius-md: 8px;
|
|
24
|
-
--radius-lg: 10px;
|
|
25
|
-
--radius-full: 50%;
|
|
26
|
-
--radius-dynamic: clamp(var(--radius-sm), 1vw, var(--radius-lg));
|
|
27
|
-
|
|
28
|
-
--color-primary: #001D68;
|
|
29
|
-
--color-primary-light: #193377;
|
|
30
|
-
--color-primary-dark: #00144A;
|
|
31
|
-
--color-secondary: #2594E3;
|
|
32
|
-
--color-secondary-light: #46A4E7;
|
|
33
|
-
--color-secondary-light-1: #c3d6e3;
|
|
34
|
-
--color-secondary-dark: #1D76B5;
|
|
35
|
-
--color-error: var(--color-red-dark);
|
|
36
|
-
--color-red: #D1232A;
|
|
37
|
-
--color-red-light: #FF2B32;
|
|
38
|
-
--color-red-dark: #D1232A;
|
|
39
|
-
--color-yellow: #FFEC19;
|
|
40
|
-
--color-yellow-light: #FFF15E;
|
|
41
|
-
--color-yellow-dark: #E5D416;
|
|
42
|
-
--color-green: #72B509;
|
|
43
|
-
--color-green-light: #8EC33A;
|
|
44
|
-
--color-green-dark: #66A208;
|
|
45
|
-
--color-purple: #33109C;
|
|
46
|
-
--color-purple-light: #4727A5;
|
|
47
|
-
--color-purple-dark: #280C7C;
|
|
48
|
-
--color-orange: #FF9505;
|
|
49
|
-
--color-orange-light: #FFA21F;
|
|
50
|
-
--color-orange-dark: #F58F00;
|
|
51
|
-
--color-white: #FFFFFF;
|
|
52
|
-
--color-black: #030711;
|
|
53
|
-
--color-gray-1: #F7F5F4;
|
|
54
|
-
--color-gray-2: #E7E5E4;
|
|
55
|
-
--color-gray-3: #D7D4D2;
|
|
56
|
-
--color-gray-4: #7C7C7C;
|
|
57
|
-
--color-gray-5: #6C6C6C;
|
|
58
|
-
--color-gray-6: #565454;
|
|
59
|
-
--color-gray-7: #424242;
|
|
60
|
-
--color-gray-8: #263238;
|
|
61
|
-
--color-gray-9: #1C2321;
|
|
12
|
+
<wral-studio mod-tag="studio-mod">
|
|
13
|
+
<studio-mod
|
|
14
|
+
name="setup"
|
|
15
|
+
src="/vellum/index.mjs"></studio-mod>
|
|
62
16
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
17
|
+
<studio-mod
|
|
18
|
+
name="example"
|
|
19
|
+
require="setup"
|
|
20
|
+
src="/vellum/example-app.mjs"></studio-mod>
|
|
67
21
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
</style>
|
|
76
|
-
<studio-app>
|
|
77
|
-
<vellum-mod name="setup" src="/vellum-fixture.mjs"></vellum-mod>
|
|
22
|
+
<studio-mod
|
|
23
|
+
name="auth"
|
|
24
|
+
require="setup"
|
|
25
|
+
api="https://api.wral.com/auth"
|
|
26
|
+
force-login
|
|
27
|
+
src="/src/index.mjs"></studio-mod>
|
|
78
28
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
force-login></vellum-mod>
|
|
82
|
-
|
|
83
|
-
</studio-app>
|
|
29
|
+
</wral-studio>
|
|
30
|
+
<body>
|
|
84
31
|
</body>
|
|
85
32
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wral/studio.mods.auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Auth mod for Studio/Vellum",
|
|
5
5
|
"main": "dist/auth.cjs.js",
|
|
6
6
|
"module": "dist/auth.es.js",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"lit": "^3.2.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@thefarce/vellum": "^0.4
|
|
24
|
+
"@thefarce/vellum": "^1.0.4",
|
|
25
|
+
"@thefarce/vellum.mods.managers.layouts.simple-web-component": "^0.2.0",
|
|
25
26
|
"eslint": "^9.22.0",
|
|
26
27
|
"eslint-plugin-jest": "^28.11.0",
|
|
27
28
|
"jest": "^29.7.0",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { html, css, LitElement } from 'lit';
|
|
2
|
-
import login from '../login.mjs';
|
|
3
2
|
import { createClient } from '@wral/sdk-auth';
|
|
4
3
|
|
|
5
4
|
const stages = Object.freeze({
|
|
@@ -8,20 +7,25 @@ const stages = Object.freeze({
|
|
|
8
7
|
FINISHED: 'finished',
|
|
9
8
|
});
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
class AuthForgotPasswordForm extends LitElement {
|
|
12
11
|
|
|
13
12
|
static get properties() {
|
|
14
13
|
return {
|
|
15
|
-
api: { type: String
|
|
16
|
-
confirmation: { type: 'String', attribute: 'confirmation' },
|
|
14
|
+
api: { type: String },
|
|
17
15
|
};
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
static get styles() {
|
|
21
19
|
return css`
|
|
22
20
|
:host {
|
|
23
|
-
display:
|
|
24
|
-
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
align-items: center;
|
|
25
|
+
}
|
|
26
|
+
form {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
25
29
|
}
|
|
26
30
|
form input[type="text"],
|
|
27
31
|
form input[type="password"] {
|
|
@@ -29,14 +33,14 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
29
33
|
margin: 5px 0 15px 0;
|
|
30
34
|
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
|
|
31
35
|
box-sizing: border-box;
|
|
32
|
-
border: 1px solid var(--color-gray-300, #
|
|
33
|
-
border-radius: var(--radius-sm,
|
|
34
|
-
color: var(--color-gray-
|
|
35
|
-
background-color: var(--color-gray-
|
|
36
|
+
border: 1px solid var(--color-gray-300, #CCC);
|
|
37
|
+
border-radius: var(--radius-sm, 3px);
|
|
38
|
+
color: var(--color-gray-700, #333);
|
|
39
|
+
background-color: var(--color-gray-50, #FFF);
|
|
36
40
|
}
|
|
37
41
|
form input[type="submit"] {
|
|
38
|
-
background-color:var(--color-primary, inherit);
|
|
39
|
-
color: var(--color-gray-
|
|
42
|
+
background-color: var(--color-primary, inherit);
|
|
43
|
+
color: var(--color-gray-50, #fff);
|
|
40
44
|
border: none;
|
|
41
45
|
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
|
|
42
46
|
border-radius: var(--radius-dynamic, 5px);
|
|
@@ -47,17 +51,16 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
47
51
|
}
|
|
48
52
|
form label {
|
|
49
53
|
font-size: 14px;
|
|
50
|
-
color: var(--color-gray-
|
|
54
|
+
color: var(--color-gray-700, #333);
|
|
51
55
|
font-weight: bold;
|
|
52
56
|
margin-bottom: var(--spacing-sm, 0.5rem);
|
|
53
57
|
}
|
|
54
58
|
.error {
|
|
55
59
|
color: var(--color-danger, red);
|
|
56
|
-
font-size:
|
|
60
|
+
font-size: 14px;
|
|
57
61
|
}
|
|
58
62
|
`;
|
|
59
63
|
}
|
|
60
|
-
|
|
61
64
|
constructor() {
|
|
62
65
|
super();
|
|
63
66
|
this.errors = {};
|
|
@@ -65,37 +68,35 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
connectedCallback() {
|
|
68
|
-
console.log('[auth]','[forgot-password]', { confirmation: this.confirmation });
|
|
69
71
|
super.connectedCallback();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.stage = stages.CONFIRM;
|
|
72
|
+
if (this.confirmToken && this.stage !== stages.FINISHED) {
|
|
73
|
+
this.setStage(stages.CONFIRM);
|
|
73
74
|
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setStage(stage) {
|
|
78
|
+
if (this.stage === stage) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.stage = stage;
|
|
74
82
|
this.requestUpdate();
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
handleSubmitRequest(e) {
|
|
78
86
|
e.preventDefault();
|
|
79
|
-
// Check for submission errors (none since username is required)
|
|
80
87
|
const username = e.target.username.value;
|
|
81
88
|
this.username = username;
|
|
82
89
|
|
|
83
90
|
// Send API Request
|
|
84
|
-
const authClient = createClient({
|
|
85
|
-
baseUrl: this.api,
|
|
86
|
-
});
|
|
91
|
+
const authClient = createClient({ baseUrl: this.api });
|
|
87
92
|
authClient.triggerResetPassword({ username }).then(() => {
|
|
88
|
-
|
|
89
|
-
console.log('[auth]', 'triggered reset password for ', username);
|
|
90
|
-
this.stage = stages.CONFIRM;
|
|
93
|
+
this.setStage(stages.CONFIRM);
|
|
91
94
|
}).catch((error) => {
|
|
92
|
-
console.error('[auth] error triggering reset password', error);
|
|
93
95
|
this.errors['general'] = error.message ||
|
|
94
|
-
|
|
96
|
+
'Unable to send password reset request. Please try again later.';
|
|
95
97
|
}).finally(() => {
|
|
96
98
|
this.requestUpdate();
|
|
97
99
|
});
|
|
98
|
-
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
handleSubmitConfirm(e) {
|
|
@@ -105,18 +106,18 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
105
106
|
const password = e.target.password.value;
|
|
106
107
|
const confirmPassword = e.target['confirm-password'].value;
|
|
107
108
|
if (password !== confirmPassword) {
|
|
108
|
-
this.errors['confirm-password'] = 'Passwords do not match
|
|
109
|
+
this.errors['confirm-password'] = 'Passwords do not match';
|
|
109
110
|
this.requestUpdate();
|
|
110
111
|
return;
|
|
111
112
|
}
|
|
112
|
-
const confirmationCode = this.
|
|
113
|
+
const confirmationCode = this.confirmToken || e.target['confirmation'].value;
|
|
113
114
|
if (!confirmationCode) {
|
|
114
|
-
this.errors['general'] = 'Confirmation code is required
|
|
115
|
+
this.errors['general'] = 'Confirmation code is required';
|
|
115
116
|
this.requestUpdate();
|
|
116
117
|
return;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
// Send API
|
|
120
|
+
// Send API Request
|
|
120
121
|
const authClient = createClient({
|
|
121
122
|
baseUrl: this.api,
|
|
122
123
|
});
|
|
@@ -125,19 +126,19 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
125
126
|
newPassword: password,
|
|
126
127
|
confirmationCode,
|
|
127
128
|
}).then(() => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
137
|
-
|
|
129
|
+
authClient.mintToken({ username: this.username, password: password })
|
|
130
|
+
.then(({token}) => {
|
|
131
|
+
this.dispatchEvent(new CustomEvent('login-success', {
|
|
132
|
+
detail: { token },
|
|
133
|
+
bubbles: true,
|
|
134
|
+
composed: true,
|
|
135
|
+
cancelable: true,
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
138
|
+
// TODO: handle issues here, probably by refreshing the page
|
|
138
139
|
}).catch((error) => {
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
this.errors['general'] = error.message ||
|
|
141
|
+
'Unable to send password reset request. Please try again later.';
|
|
141
142
|
}).finally(() => {
|
|
142
143
|
this.requestUpdate();
|
|
143
144
|
});
|
|
@@ -145,13 +146,10 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
145
146
|
|
|
146
147
|
render() {
|
|
147
148
|
switch (this.stage) {
|
|
148
|
-
case stages.CONFIRM:
|
|
149
|
-
return this.renderConfirmForm();
|
|
150
149
|
case stages.FINISHED:
|
|
151
150
|
return html`<p>Your password has been reset.</p>`;
|
|
152
|
-
case stages.
|
|
153
|
-
return
|
|
154
|
-
resetting your password.</p>`;
|
|
151
|
+
case stages.CONFIRM:
|
|
152
|
+
return this.renderConfirmForm();
|
|
155
153
|
case stages.REQUEST:
|
|
156
154
|
default:
|
|
157
155
|
return this.renderRequestForm();
|
|
@@ -159,7 +157,8 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
renderRequestForm() {
|
|
162
|
-
return html`<form @submit
|
|
160
|
+
return html`<form @submit="${this.handleSubmitRequest}">
|
|
161
|
+
<p>Enter your username or email address to request a password reset.</p>
|
|
163
162
|
<label for="username">Username/Email</label>
|
|
164
163
|
<input
|
|
165
164
|
type="text"
|
|
@@ -168,22 +167,23 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
168
167
|
required
|
|
169
168
|
/>
|
|
170
169
|
<p class="error">${this.errors['general']}</p>
|
|
171
|
-
<input type="submit" value="Request
|
|
170
|
+
<input type="submit" value="Request Password Reset" />
|
|
172
171
|
</form>`;
|
|
173
172
|
}
|
|
174
173
|
|
|
174
|
+
|
|
175
175
|
renderConfirmForm() {
|
|
176
|
-
return html`<form @submit
|
|
177
|
-
|
|
176
|
+
return html`<form @submit="${this.handleSubmitConfirm}">
|
|
177
|
+
<p>You should have received a confirmation code to reset your password.</p>
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
${this.confirmToken ? ''
|
|
180
180
|
: html`<label for="confirmation">Confirmation Code</label>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
<input
|
|
182
|
+
type="text"
|
|
183
|
+
name="confirmation"
|
|
184
|
+
placeholder="Confirmation Code"
|
|
185
|
+
required
|
|
186
|
+
/>`}
|
|
187
187
|
|
|
188
188
|
<label for="password">New Password</label>
|
|
189
189
|
<input
|
|
@@ -192,10 +192,6 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
192
192
|
placeholder="Password"
|
|
193
193
|
required
|
|
194
194
|
/>
|
|
195
|
-
${this.errors['password']
|
|
196
|
-
? html`<p class="error">${this.errors['password']}</p>`
|
|
197
|
-
: ''}
|
|
198
|
-
|
|
199
195
|
<label for="confirm-password">Confirm Password</label>
|
|
200
196
|
<input
|
|
201
197
|
type="password"
|
|
@@ -204,14 +200,18 @@ export class ForgotPasswordForm extends LitElement {
|
|
|
204
200
|
required
|
|
205
201
|
/>
|
|
206
202
|
${this.errors['confirm-password']
|
|
207
|
-
? html`<p class="error">${this.errors['confirm-password']}</p>`
|
|
208
|
-
: ''}
|
|
203
|
+
? html`<p class="error">${this.errors['confirm-password']}</p>` : ''}
|
|
209
204
|
|
|
210
205
|
${this.errors['general']
|
|
211
|
-
? html`<p class="error">${this.errors['general']}</p>`
|
|
212
|
-
|
|
206
|
+
? html`<p class="error">${this.errors['general']}</p>` : ''}
|
|
207
|
+
|
|
213
208
|
<input type="submit" value="Reset Password" />
|
|
214
209
|
</form>`;
|
|
215
210
|
}
|
|
216
211
|
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!customElements.get('auth-forgot-password-form')) {
|
|
216
|
+
window.customElements.define('auth-forgot-password-form', AuthForgotPasswordForm);
|
|
217
217
|
}
|
package/src/helper.mjs
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* an API token.
|
|
4
|
-
* @example <code>
|
|
5
|
-
* async () => {
|
|
6
|
-
* const myElem = document.querySelector('my-element');
|
|
7
|
-
* const token = await getToken(myElem);
|
|
8
|
-
* someApiCall({ token, ... });
|
|
9
|
-
* }
|
|
10
|
-
* </code>
|
|
11
|
-
* @param {Element} eventTarget - a DOM element that will trigger the event
|
|
12
|
-
* @returns {Promise} - a promise that resolves with the token
|
|
2
|
+
* These are helper functions for consumption by other modules.
|
|
13
3
|
*/
|
|
4
|
+
|
|
14
5
|
export function getToken(eventTarget) {
|
|
15
6
|
return new Promise((resolve, reject) => {
|
|
16
|
-
if (!eventTarget
|
|
17
|
-
reject(new Error('getToken must be called with
|
|
7
|
+
if (!eventTarget?.dispatchEvent) {
|
|
8
|
+
reject(new Error('getToken must be called with an eventTarget or DOM element'));
|
|
18
9
|
}
|
|
19
10
|
eventTarget.dispatchEvent(new CustomEvent('harness:action', {
|
|
20
11
|
detail: {
|
|
21
12
|
type: 'auth:requestToken',
|
|
22
|
-
detail: {
|
|
23
|
-
callback: resolve,
|
|
24
|
-
},
|
|
13
|
+
detail: { callback: resolve },
|
|
25
14
|
},
|
|
26
15
|
bubbles: true,
|
|
27
16
|
composed: true,
|
package/src/index.mjs
CHANGED
|
@@ -1,17 +1,113 @@
|
|
|
1
|
-
import { getToken as
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { getToken as dispatchTokenRequest } from './helper.mjs';
|
|
2
|
+
import authLayout from './layout.mjs';
|
|
3
|
+
import loginForm from './login-form.mjs';
|
|
4
|
+
import {
|
|
5
|
+
setToken,
|
|
6
|
+
getFreshToken,
|
|
7
|
+
destroyToken,
|
|
8
|
+
} from './token.mjs';
|
|
4
9
|
|
|
5
10
|
export function init(toolkit, mod) {
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
|
|
12
|
+
const modName = 'mod-auth';
|
|
13
|
+
const debug = mod?.element?.attributes?.debug?.value || false;
|
|
14
|
+
const forceLogin = mod?.element?.attributes?.['force-login']?.value || false;
|
|
15
|
+
|
|
16
|
+
const state = {
|
|
17
|
+
modName,
|
|
18
|
+
toolkit,
|
|
19
|
+
apiBaseUrl: mod?.element?.attributes?.api?.value || 'https://api.wral.com/auth',
|
|
20
|
+
debug: debug ? (...args) => console.log('[auth]',...args) : () => {},
|
|
21
|
+
tokenKey: `${modName}::token`,
|
|
22
|
+
callbacks: [],
|
|
23
|
+
isLoginPresent: false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Actions
|
|
27
|
+
Object.entries({
|
|
28
|
+
'auth:requestToken': requestToken,
|
|
29
|
+
'auth:destroy': destroy,
|
|
30
|
+
}).forEach(([actionType, fn]) => {
|
|
31
|
+
const debug = (...args) => state.debug('[action]', actionType, ...args);
|
|
32
|
+
toolkit.dispatchAction({
|
|
33
|
+
type: 'action:register',
|
|
34
|
+
detail: {
|
|
35
|
+
actionType,
|
|
36
|
+
modName,
|
|
37
|
+
handler: (...args) => {
|
|
38
|
+
debug('handler', ...args);
|
|
39
|
+
fn(state, ...args);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
15
42
|
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Auth Layout - secure layout containing only the auth forms
|
|
46
|
+
toolkit.dispatchAction({
|
|
47
|
+
type: 'layout:register',
|
|
48
|
+
detail: {
|
|
49
|
+
name: `${modName}::auth-layout`,
|
|
50
|
+
templateFn: authLayout,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (forceLogin) {
|
|
55
|
+
dispatchTokenRequest(mod.element);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Handler for the `auth:requestToken` action
|
|
62
|
+
*/
|
|
63
|
+
async function requestToken(state, eventDetail) {
|
|
64
|
+
const { callback } = eventDetail;
|
|
65
|
+
let token;
|
|
66
|
+
try {
|
|
67
|
+
token = await getFreshToken(state);
|
|
68
|
+
callback(token);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.log('Presenting login form to get a fresh token', err);
|
|
71
|
+
state.callbacks.push(callback);
|
|
72
|
+
if (!state.isLoginPresent) {
|
|
73
|
+
presentLoginForm(state);
|
|
74
|
+
}
|
|
16
75
|
}
|
|
76
|
+
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
function destroy(state) {
|
|
81
|
+
destroyToken(state);
|
|
82
|
+
// TODO: destroy the layouts and present a low-security layout
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function presentLoginForm(state) {
|
|
86
|
+
state.isLoginPresent = true;
|
|
87
|
+
state.toolkit.dispatchAction({
|
|
88
|
+
type: 'layout:push',
|
|
89
|
+
detail: {
|
|
90
|
+
name: `${state.modName}::auth-layout`,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
state.toolkit.dispatchAction({
|
|
94
|
+
type: 'layout:content:append',
|
|
95
|
+
detail: {
|
|
96
|
+
content: loginForm(state, loginSuccessHandler(state)),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
17
99
|
}
|
|
100
|
+
|
|
101
|
+
function loginSuccessHandler(state) {
|
|
102
|
+
return (event) => {
|
|
103
|
+
const { token } = event.detail;
|
|
104
|
+
setToken(state, token);
|
|
105
|
+
state.callbacks.forEach(callback => callback(token));
|
|
106
|
+
state.callbacks = [];
|
|
107
|
+
state.isLoginPresent = false;
|
|
108
|
+
state.toolkit.dispatchAction({
|
|
109
|
+
type: 'layout:pop',
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
package/src/layout.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
|
|
3
|
+
export class ModAuthLayout extends LitElement {
|
|
4
|
+
static get styles() {
|
|
5
|
+
return css`
|
|
6
|
+
.login-layout {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: 1rem;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
align-items: center;
|
|
12
|
+
min-height: 100vh;
|
|
13
|
+
background-color: var(--color-gray-100);
|
|
14
|
+
}
|
|
15
|
+
.login-layout > * {
|
|
16
|
+
max-width: 400px;
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
render() {
|
|
22
|
+
return html`
|
|
23
|
+
<div class="login-layout">
|
|
24
|
+
<slot name="main"></slot>
|
|
25
|
+
</div>
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (customElements.get('mod-auth-layout') === undefined) {
|
|
31
|
+
customElements.define('mod-auth-layout', ModAuthLayout);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function templateFn() {
|
|
35
|
+
return html`<mod-auth-layout></mod-auth-layout>`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default templateFn;
|