@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
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { LitElement, html, css } from 'lit';
|
|
2
|
-
import { getToken } from '../helper.mjs';
|
|
3
|
-
import { createClient } from '@wral/sdk-auth';
|
|
4
|
-
|
|
5
|
-
export function render(state, auth) {
|
|
6
|
-
return html`
|
|
7
|
-
<auth-change-password-form .auth=${auth}></auth-change-password-form>
|
|
8
|
-
`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class ChangePasswordForm extends LitElement {
|
|
12
|
-
|
|
13
|
-
static get styles() {
|
|
14
|
-
return css`
|
|
15
|
-
:host {
|
|
16
|
-
color: var(--color-text);
|
|
17
|
-
display: block;
|
|
18
|
-
font-family: var(--font-body);
|
|
19
|
-
}
|
|
20
|
-
h1,h2,h3,h4,h5,h6,label { font-family: var(--font-heading) }
|
|
21
|
-
label {
|
|
22
|
-
display: block;
|
|
23
|
-
font-size: var(--font-size-base);
|
|
24
|
-
margin-top: var(--spacing-md, 0.5rem);
|
|
25
|
-
}
|
|
26
|
-
input[type="text"],
|
|
27
|
-
input[type="password"] {
|
|
28
|
-
width: 100%;
|
|
29
|
-
margin: 5px 0 15px 0;
|
|
30
|
-
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
|
|
31
|
-
box-sizing: border-box;
|
|
32
|
-
border: 1px solid var(--color-gray-300, #ccc);
|
|
33
|
-
border-radius: var(--radius-sm, 5px);
|
|
34
|
-
color: var(--color-gray-9, #333);
|
|
35
|
-
background-color: var(--color-gray-0, transparent);
|
|
36
|
-
}
|
|
37
|
-
input[type="submit"] {
|
|
38
|
-
background-color: var(--color-primary, inherit);
|
|
39
|
-
color: var(--color-gray-0, #fff);
|
|
40
|
-
border: none;
|
|
41
|
-
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
|
|
42
|
-
border-radius: var(--radius-dynamic, 5px);
|
|
43
|
-
width: 200px;
|
|
44
|
-
cursor: pointer;
|
|
45
|
-
}
|
|
46
|
-
.error {
|
|
47
|
-
color: var(--color-error);
|
|
48
|
-
}
|
|
49
|
-
`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static get properties() {
|
|
53
|
-
return {
|
|
54
|
-
auth: { type: Object },
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
constructor() {
|
|
59
|
-
super();
|
|
60
|
-
this.errors = {};
|
|
61
|
-
this.success = null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
connectedCallback() {
|
|
65
|
-
super.connectedCallback();
|
|
66
|
-
// discard result, we're only interested in the side-effect of being logged in
|
|
67
|
-
getToken(this);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async onSubmit(e) {
|
|
71
|
-
e.preventDefault();
|
|
72
|
-
|
|
73
|
-
// Ensure passwords match
|
|
74
|
-
if (e.target.newPassword.value !== e.target.confirmPassword.value) {
|
|
75
|
-
this.errors['confirmPassword'] = 'Passwords do not match';
|
|
76
|
-
this.requestUpdate();
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Check that new password is actually different
|
|
81
|
-
if (e.target.newPassword.value === e.target.currentPassword.value) {
|
|
82
|
-
this.errors['newPassword'] = 'The new password is the same as your '
|
|
83
|
-
+ 'current password.';
|
|
84
|
-
this.requestUpdate();
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const apiKey = await getToken(this);
|
|
89
|
-
|
|
90
|
-
// TODO: send request to API
|
|
91
|
-
const authClient = createClient({
|
|
92
|
-
baseUrl: this.auth.config.api,
|
|
93
|
-
apiKey,
|
|
94
|
-
});
|
|
95
|
-
await authClient.updatePassword({
|
|
96
|
-
currentPassword: e.target.currentPassword.value,
|
|
97
|
-
newPassword: e.target.newPassword.value,
|
|
98
|
-
}).then(() => {
|
|
99
|
-
this.errors = {};
|
|
100
|
-
this.success = 'Password changed.';
|
|
101
|
-
this.requestUpdate();
|
|
102
|
-
}).catch((error) => {
|
|
103
|
-
this.errors['submit'] = error.message;
|
|
104
|
-
this.requestUpdate();
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
render() {
|
|
109
|
-
return html`
|
|
110
|
-
<h1>Change your password</h1>
|
|
111
|
-
<p>Enter your current password and choose a new password.</p>
|
|
112
|
-
<form @submit=${this.onSubmit}>
|
|
113
|
-
<div class="field">
|
|
114
|
-
<label for="currentPassword">Current Password</label>
|
|
115
|
-
<input
|
|
116
|
-
type="password"
|
|
117
|
-
name="currentPassword"
|
|
118
|
-
placeholder="Current Password"
|
|
119
|
-
required
|
|
120
|
-
/>
|
|
121
|
-
${this.errors['currentPassword']
|
|
122
|
-
? html`<p class="error">${this.errors['currentPassword']}</p>` : ''}
|
|
123
|
-
</div>
|
|
124
|
-
<div class="field">
|
|
125
|
-
<label for="newPassword">New Password</label>
|
|
126
|
-
<input
|
|
127
|
-
type="password"
|
|
128
|
-
name="newPassword"
|
|
129
|
-
placeholder="New Password"
|
|
130
|
-
required
|
|
131
|
-
/>
|
|
132
|
-
${this.errors['newPassword']
|
|
133
|
-
? html`<p class="error">${this.errors['newPassword']}</p>` : ''}
|
|
134
|
-
</div>
|
|
135
|
-
<div class="field">
|
|
136
|
-
<label for="confirmPassword">Confirm Password</label>
|
|
137
|
-
<input
|
|
138
|
-
type="password"
|
|
139
|
-
name="confirmPassword"
|
|
140
|
-
placeholder="Confirm Password"
|
|
141
|
-
required
|
|
142
|
-
/>
|
|
143
|
-
${this.errors['confirmPassword']
|
|
144
|
-
? html`<p class="error">${this.errors['confirmPassword']}</p>` : ''}
|
|
145
|
-
</div>
|
|
146
|
-
<input type="submit" value="Change Password" />
|
|
147
|
-
${this.errors['submit']
|
|
148
|
-
? html`<p class="error">${this.errors['submit']}</p>` : ''}
|
|
149
|
-
${this.success ? html`<p class="success">${this.success}</p>` : ''}
|
|
150
|
-
</form>
|
|
151
|
-
`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!customElements.get('auth-change-password-form')) {
|
|
157
|
-
customElements.define('auth-change-password-form', ChangePasswordForm);
|
|
158
|
-
}
|
package/src/routes/dashboard.mjs
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { html } from 'lit';
|
|
2
|
-
import { pushState } from '../state.mjs';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export function render(state, auth) {
|
|
6
|
-
const { history } = auth.config.toolkit.element.ownerDocument.defaultView;
|
|
7
|
-
const navigateTo = (path, title) => () => {
|
|
8
|
-
pushState(auth, history, { path, title });
|
|
9
|
-
};
|
|
10
|
-
return html`
|
|
11
|
-
<h1>Auth Dashboard</h1>
|
|
12
|
-
<ul>
|
|
13
|
-
<li><a href="#" @click=${navigateTo('/change-password')}>Change Password</a></li>
|
|
14
|
-
<li><a href="#" @click=${auth.handleDestroyAuth}>Logout</a></li>
|
|
15
|
-
</ul>
|
|
16
|
-
`;
|
|
17
|
-
}
|
package/src/routes/index.mjs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import * as dashboard from './dashboard.mjs';
|
|
2
|
-
import * as changePassword from './change-password.mjs';
|
|
3
|
-
|
|
4
|
-
export const routes = [
|
|
5
|
-
{ path: '/', render: dashboard.render },
|
|
6
|
-
{ path: '/change-password', render: changePassword.render },
|
|
7
|
-
];
|
|
8
|
-
|
|
9
|
-
export function renderRoute(state, auth) {
|
|
10
|
-
const { path } = state;
|
|
11
|
-
const route = routes.find((r) => r.path === path);
|
|
12
|
-
if (route) {
|
|
13
|
-
return route.render(state, auth);
|
|
14
|
-
}
|
|
15
|
-
}
|
package/src/state.mjs
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ensure pathname has a consistent format, with a leading slash, and no
|
|
3
|
-
* trailing slash. This lets the application be more tolerant of input.
|
|
4
|
-
* @param {string} pathname
|
|
5
|
-
* @returns {string}
|
|
6
|
-
*/
|
|
7
|
-
export function coercePathname(pathname) {
|
|
8
|
-
return (pathname.startsWith('/') ? pathname : `/${pathname}`) // ensure leading slash
|
|
9
|
-
.replace(/\/{2,}/g, '/') // remove consecutive slashes
|
|
10
|
-
.replace(/(?<=.)\/$/, ''); // remove trailing slash (not at beginning)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Push a new state to the history (presumably window.history).
|
|
15
|
-
* After the application changes state, call pushState to enable this history
|
|
16
|
-
* to be tracked.
|
|
17
|
-
*
|
|
18
|
-
* To take advantage of the forward and back button behavior, be sure to handle
|
|
19
|
-
* window.onpopstate events.
|
|
20
|
-
* @param {Object} auth
|
|
21
|
-
* @param {History} history
|
|
22
|
-
* @param {Object} state
|
|
23
|
-
* @returns {void}
|
|
24
|
-
*/
|
|
25
|
-
export function pushState(auth, history, state) {
|
|
26
|
-
const pathname = stateToPathname(auth, state);
|
|
27
|
-
history.pushState(state, state.title, pathname);
|
|
28
|
-
auth.requestRender(state);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Produce a state object from a pathname
|
|
33
|
-
* @param {Object} auth
|
|
34
|
-
* @param {string} pathname
|
|
35
|
-
* @returns {Object}
|
|
36
|
-
*/
|
|
37
|
-
export function pathnameToState(auth, pathname) {
|
|
38
|
-
const { mount } = auth.config;
|
|
39
|
-
const { history } = auth.config.toolkit.element.ownerDocument.defaultView;
|
|
40
|
-
|
|
41
|
-
// no state if pathname is not prefixed with mount
|
|
42
|
-
if (!pathname.startsWith(mount)) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
// remove mount from pathname
|
|
46
|
-
return {
|
|
47
|
-
path: coercePathname(pathname.slice(mount.length)),
|
|
48
|
-
push: (state) => pushState(auth, history, state),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Produce a pathname from a state object
|
|
54
|
-
* @param {Object} auth
|
|
55
|
-
* @param {Object} state
|
|
56
|
-
* @returns {string}
|
|
57
|
-
*/
|
|
58
|
-
export function stateToPathname(auth, state) {
|
|
59
|
-
const { mount } = auth.config;
|
|
60
|
-
return coercePathname(`${mount}/${state.path}`);
|
|
61
|
-
}
|
package/src/state.test.mjs
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
coercePathname,
|
|
3
|
-
pushState,
|
|
4
|
-
pathnameToState,
|
|
5
|
-
} from './state.mjs';
|
|
6
|
-
import { jest } from '@jest/globals';
|
|
7
|
-
|
|
8
|
-
describe('state', () => {
|
|
9
|
-
|
|
10
|
-
describe('coercePathname', () => {
|
|
11
|
-
// input, expected
|
|
12
|
-
const cases = [
|
|
13
|
-
['', '/'],
|
|
14
|
-
['//', '/'],
|
|
15
|
-
['auth', '/auth'],
|
|
16
|
-
['foo/bar', '/foo/bar'],
|
|
17
|
-
['foo//bar', '/foo/bar'],
|
|
18
|
-
['foo//bar/', '/foo/bar'],
|
|
19
|
-
];
|
|
20
|
-
cases.forEach(([input, expected]) => {
|
|
21
|
-
it(`should coerce ${input} to ${expected}`, () => {
|
|
22
|
-
expect(coercePathname(input)).toBe(expected);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('pushState', () => {
|
|
28
|
-
it('pushes a state object to history', () => {
|
|
29
|
-
const history = {
|
|
30
|
-
pushState: jest.fn(),
|
|
31
|
-
};
|
|
32
|
-
const auth = {
|
|
33
|
-
requestRender: jest.fn(),
|
|
34
|
-
config: { mount: 'foo' },
|
|
35
|
-
};
|
|
36
|
-
const newState = {
|
|
37
|
-
title: 'Some Title',
|
|
38
|
-
path: 'bar',
|
|
39
|
-
};
|
|
40
|
-
pushState(auth, history, newState);
|
|
41
|
-
expect(history.pushState)
|
|
42
|
-
.toHaveBeenCalledWith(newState, newState.title, '/foo/bar');
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe.skip('pathnameToState', () => {
|
|
47
|
-
// TODO: refactor to not expect auth.config.toolkit.element.ownerDocument.defaultView
|
|
48
|
-
it('produces a state object from a pathname', () => {
|
|
49
|
-
const auth = {
|
|
50
|
-
config: { mount: 'foo' },
|
|
51
|
-
};
|
|
52
|
-
const pathname = '/foo/bar';
|
|
53
|
-
expect(pathnameToState(auth, pathname)).toEqual({
|
|
54
|
-
path: 'bar',
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
package/src/styles.mjs
DELETED
package/src/utils.mjs
DELETED
package/vellum-fixture.mjs
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This is a Vellum fixture application that resembles an environment that would
|
|
3
|
-
* use this Auth mod.
|
|
4
|
-
*/
|
|
5
|
-
import {
|
|
6
|
-
VellumHarness,
|
|
7
|
-
VellumModLoader,
|
|
8
|
-
initLayoutManager,
|
|
9
|
-
initThemeManager,
|
|
10
|
-
} from '@thefarce/vellum';
|
|
11
|
-
import { html, css } from 'lit';
|
|
12
|
-
|
|
13
|
-
export function init(...args) {
|
|
14
|
-
initLayoutManager(...args);
|
|
15
|
-
initThemeManager(...args);
|
|
16
|
-
const [ toolkit ] = args;
|
|
17
|
-
toolkit.dispatchAction({
|
|
18
|
-
type: 'layout:register',
|
|
19
|
-
detail: {
|
|
20
|
-
name: 'menu-main-task-layout',
|
|
21
|
-
slots: ['menu','main','task'],
|
|
22
|
-
templateFn: layoutTemplate,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
toolkit.dispatchAction({
|
|
26
|
-
type: 'layout:push',
|
|
27
|
-
detail: { layout: 'menu-main-task-layout' },
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/* Define some core web components */
|
|
33
|
-
Object.entries({
|
|
34
|
-
'studio-app': VellumHarness,
|
|
35
|
-
'studio-mod': VellumModLoader,
|
|
36
|
-
}).forEach(([name, webComponent]) => {
|
|
37
|
-
if (!customElements.get(name)) {
|
|
38
|
-
customElements.define(name, webComponent);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
function layoutTemplate(slots) {
|
|
43
|
-
const styles = css`
|
|
44
|
-
.menu-main-task-layout {
|
|
45
|
-
display: flex;
|
|
46
|
-
height: 100vh;
|
|
47
|
-
}
|
|
48
|
-
.menu-main-task-layout > .toolbar-slot {
|
|
49
|
-
position: fixed;
|
|
50
|
-
width: var(--menu-width, 200px);
|
|
51
|
-
background-color: var(--color-gray-100, #f7f5f4);
|
|
52
|
-
height: 100vh;
|
|
53
|
-
box-sizing: border-box;
|
|
54
|
-
padding: var(--spacing-md, 1rem);
|
|
55
|
-
}
|
|
56
|
-
.menu-main-task-layout > .main-slot {
|
|
57
|
-
background-color: var(--color-gray-0, #fff);
|
|
58
|
-
flex-grow: 1;
|
|
59
|
-
margin-left: var(--menu-width, 250px);
|
|
60
|
-
padding: var(--spacing-lg, 2rem);
|
|
61
|
-
}
|
|
62
|
-
.menu-main-task-layout > .task-slot {
|
|
63
|
-
padding: var(--spacing-lg, 2rem);
|
|
64
|
-
box-sizing: border-box;
|
|
65
|
-
background-color: var(--color-gray-100, #f7f5f4);
|
|
66
|
-
}
|
|
67
|
-
`;
|
|
68
|
-
return html`
|
|
69
|
-
<style>${styles}</style>
|
|
70
|
-
<div class="menu-main-task-layout">
|
|
71
|
-
<aside class="toolbar-slot">
|
|
72
|
-
<p>Application Menu</p>
|
|
73
|
-
${slots.menu && slots.menu.length > 0
|
|
74
|
-
? slots.menu.map(item => item)
|
|
75
|
-
: html``}
|
|
76
|
-
</aside>
|
|
77
|
-
<main class="main-slot">
|
|
78
|
-
${slots.main && slots.main.length > 0
|
|
79
|
-
? slots.main.map(item => item)
|
|
80
|
-
: html``}
|
|
81
|
-
</main>
|
|
82
|
-
${slots.task && slots.task.length > 0
|
|
83
|
-
? html`<div class="task-slot">${slots.task.map(item => item)}</div>`
|
|
84
|
-
: html``}
|
|
85
|
-
</div>`;
|
|
86
|
-
}
|