@wral/studio.mods.auth 0.3.7 → 1.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 +39 -47
- package/bitbucket-pipelines.yml +25 -1
- package/dist/auth.cjs.js +326 -1467
- package/dist/auth.es.js +1081 -3093
- package/dist/lib.cjs.js +1 -1
- package/dist/lib.es.js +13 -7
- package/eslint.config.mjs +41 -34
- package/index.html +83 -18
- package/jest.config.mjs +24 -0
- package/jest.setup.mjs +5 -0
- package/package.json +15 -28
- package/src/auth.mjs +204 -69
- package/src/auth.test.mjs +97 -0
- package/src/components/auth-app.mjs +26 -0
- package/src/components/forgot-password-form.mjs +217 -0
- package/src/components/login-form.mjs +288 -0
- package/src/config.mjs +27 -0
- package/src/helper.mjs +31 -0
- package/src/helper.test.mjs +44 -0
- package/src/index.mjs +17 -0
- package/src/login-layout.mjs +32 -0
- package/src/login.mjs +20 -0
- package/src/routes/change-password.mjs +158 -0
- package/src/routes/dashboard.mjs +17 -0
- package/src/routes/index.mjs +15 -0
- package/src/state.mjs +61 -0
- package/src/state.test.mjs +58 -0
- package/src/styles.mjs +9 -0
- package/src/token.mjs +40 -0
- package/src/utils.mjs +3 -0
- package/vellum-fixture.mjs +86 -0
- package/vite.config.mjs +12 -0
- package/components.html +0 -43
- package/development.md +0 -41
- package/src/components/mod-auth-login-form.mjs +0 -133
- package/src/components/studio-change-password.mjs +0 -84
- package/src/components/studio-login.mjs +0 -94
- package/src/components/studio-profile-view.mjs +0 -56
- package/src/components/studio-reset-password.mjs +0 -110
- package/src/lib.mjs +0 -16
- package/src/tool-dummy.mjs +0 -84
- package/src/util.mjs +0 -194
- package/src/util.test.mjs +0 -171
- package/vite.config.js +0 -12
- package/web-test-runner.config.mjs +0 -28
package/src/util.mjs
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Module for authentication management using `@wral/sdk-auth` and session
|
|
3
|
-
* storage. Provides functionalities to handle login, logout, token refresh,
|
|
4
|
-
* and decode operations.
|
|
5
|
-
* @module authTool
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createClient } from '@wral/sdk-auth';
|
|
9
|
-
import { html } from 'lit';
|
|
10
|
-
import './components/mod-auth-login-form.mjs';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Retrieves a valid authentication token from session storage or initiates
|
|
14
|
-
* a new token request.
|
|
15
|
-
*
|
|
16
|
-
* @param {Object} params - The function parameters
|
|
17
|
-
* @param {Object} params.studio - The studio context to raise or handle events
|
|
18
|
-
* @returns {Promise<string>} promise that resolves with the token if successful
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* // Request token within a studio context
|
|
22
|
-
* getToken({ studio: myStudioContext })
|
|
23
|
-
* .then(token => console.log('Authentication Token:', token))
|
|
24
|
-
* .catch(error => console.error('Failed to retrieve token:', error));
|
|
25
|
-
*/
|
|
26
|
-
export function getToken({ studio, config }) {
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
let token = window.sessionStorage.getItem(tokenStorageKey({ studio }));
|
|
29
|
-
if (token) {
|
|
30
|
-
// Token exists, make sure its fresh and resolve
|
|
31
|
-
if (shouldRefreshToken(token)) {
|
|
32
|
-
refreshToken({ studio, config, token }).then(resolve).catch((error) => {
|
|
33
|
-
// Log a token refresh failure
|
|
34
|
-
/* eslint-disable-next-line no-console */
|
|
35
|
-
console.error('Failed to refresh token:', error);
|
|
36
|
-
// Remove token if refresh fails
|
|
37
|
-
window.sessionStorage.removeItem(tokenStorageKey({ studio }));
|
|
38
|
-
getToken({ studio, config }).then(resolve).catch(reject);
|
|
39
|
-
});
|
|
40
|
-
} else {
|
|
41
|
-
resolve(token);
|
|
42
|
-
}
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const storeToken = (token) => {
|
|
47
|
-
window.sessionStorage.setItem(tokenStorageKey({ studio }), token);
|
|
48
|
-
return token;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
let loginForm;
|
|
52
|
-
|
|
53
|
-
// Token doesn't exist, present the login form
|
|
54
|
-
const successfulLogin = (event) => {
|
|
55
|
-
const { token } = event.detail;
|
|
56
|
-
storeToken(token);
|
|
57
|
-
resolve(token);
|
|
58
|
-
studio.pub('system::workspace', 'dismiss', loginForm);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
loginForm = html`<mod-auth-login-form
|
|
62
|
-
id="login"
|
|
63
|
-
apibaseurl=${config.apibaseurl}
|
|
64
|
-
@login-success=${successfulLogin}></mod-auth-login-form>`;
|
|
65
|
-
|
|
66
|
-
studio.pub('system::workspace', 'present', loginForm);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Refreshes an expired authentication token.
|
|
72
|
-
*
|
|
73
|
-
* @param {Object} params - The function parameters.
|
|
74
|
-
* @param {Object} params.studio - The studio context.
|
|
75
|
-
* @param {string} params.token - The expired token.
|
|
76
|
-
* @returns {Promise<string>} A promise that resolves with the refreshed token.
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* // Refresh an expired token
|
|
80
|
-
* refreshToken({ studio: myStudioContext, token: 'expiredToken123' })
|
|
81
|
-
* .then(newToken => console.log('Token refreshed:', newToken))
|
|
82
|
-
* .catch(error => console.error('Failed to refresh token:', error));
|
|
83
|
-
*/
|
|
84
|
-
export async function refreshToken({ studio, config, token }) {
|
|
85
|
-
const authApiUrl = config.apibaseurl;
|
|
86
|
-
const client = createClient({ baseUrl: authApiUrl });
|
|
87
|
-
|
|
88
|
-
const { token: newToken } = await client.refreshToken({ token });
|
|
89
|
-
window.sessionStorage.setItem(tokenStorageKey({ studio }), newToken);
|
|
90
|
-
return newToken;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Logs out the current user by removing the authentication token from session
|
|
95
|
-
* storage.
|
|
96
|
-
*
|
|
97
|
-
* @param {Object} params - The function parameters.
|
|
98
|
-
* @param {Object} params.studio - The studio context to raise or handle events.
|
|
99
|
-
*
|
|
100
|
-
* @example
|
|
101
|
-
* // Logout from the system
|
|
102
|
-
* logout({ studio: myStudioContext });
|
|
103
|
-
*/
|
|
104
|
-
export function logout({ studio }) {
|
|
105
|
-
window.sessionStorage.removeItem(tokenStorageKey({ studio }));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Decodes a JWT (JSON Web Token) to extract its payload as an object.
|
|
110
|
-
*
|
|
111
|
-
* @param {string} token - The JWT to decode.
|
|
112
|
-
* @returns {Object} The decoded payload of the token.
|
|
113
|
-
*
|
|
114
|
-
* @example
|
|
115
|
-
* // Decode a sample JWT
|
|
116
|
-
* const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
|
|
117
|
-
* + '.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2M'
|
|
118
|
-
* + 'jM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
119
|
-
* const payload = decodeToken(token);
|
|
120
|
-
* console.log('Decoded Payload:', payload);
|
|
121
|
-
*/
|
|
122
|
-
export function decodeToken(token) {
|
|
123
|
-
return JSON.parse(window.atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Determines if the token needs to be refreshed based on its expiry time.
|
|
128
|
-
*
|
|
129
|
-
* @param {string} token - The authentication token to check.
|
|
130
|
-
* @param {number} [offset=300] - The offset in milliseconds to consider
|
|
131
|
-
* for refreshing before actual expiry.
|
|
132
|
-
* @param {number} [now=Date.now()] - The current timestamp, primarily used
|
|
133
|
-
* for testing.
|
|
134
|
-
* @returns {boolean} True if the token needs to be refreshed, false otherwise.
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* // Check if token needs to be refreshed
|
|
138
|
-
* const tokenNeedsRefresh = shouldRefreshToken('someToken', 300);
|
|
139
|
-
* console.log('Token needs refresh:', tokenNeedsRefresh);
|
|
140
|
-
*/
|
|
141
|
-
function shouldRefreshToken(token, offset = 300, now = Date.now()) {
|
|
142
|
-
const decoded = decodeToken(token);
|
|
143
|
-
return decoded.exp * 1000 - offset < now;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Generates a unique key for storing the token in session storage.
|
|
148
|
-
*
|
|
149
|
-
* @param {Object} params - The function parameters.
|
|
150
|
-
* @param {Object} params.studio - The studio context.
|
|
151
|
-
* @returns {string} The storage key for the token.
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* // Get storage key
|
|
155
|
-
* const key = tokenStorageKey({ studio: myStudioContext });
|
|
156
|
-
* console.log('Storage Key:', key);
|
|
157
|
-
*/
|
|
158
|
-
function tokenStorageKey () {
|
|
159
|
-
return `studio.wral::auth_token`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Converts an error object into a user-friendly message.
|
|
164
|
-
*
|
|
165
|
-
* @param {Error} error - The error object to convert.
|
|
166
|
-
* @returns {string} The user-friendly message for the error.
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* // Convert an error object into a user-friendly message
|
|
170
|
-
* const error = new Error('An error occurred');
|
|
171
|
-
* const message = messageFromError(error);
|
|
172
|
-
* console.log('Error Message:', message);
|
|
173
|
-
*/
|
|
174
|
-
export function messageFromError (error) {
|
|
175
|
-
switch (error.status) {
|
|
176
|
-
// Let's make these messages _very_ user friendly.
|
|
177
|
-
case 401:
|
|
178
|
-
return 'The username or password provided is incorrect.';
|
|
179
|
-
case 403:
|
|
180
|
-
return 'You do not have permission to perform this action.';
|
|
181
|
-
case 404:
|
|
182
|
-
return 'The requested resource could not be found.';
|
|
183
|
-
case 500:
|
|
184
|
-
return 'An internal server error has occurred.';
|
|
185
|
-
case 503:
|
|
186
|
-
return 'The server is temporarily unavailable. Please try again later.';
|
|
187
|
-
case 504:
|
|
188
|
-
return 'The server took too long to respond. Please try again later.';
|
|
189
|
-
case 0:
|
|
190
|
-
return 'The server could not be reached. Please try again later.';
|
|
191
|
-
default:
|
|
192
|
-
return error.message || 'An unknown error has occurred.';
|
|
193
|
-
}
|
|
194
|
-
}
|
package/src/util.test.mjs
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import sinon from 'sinon';
|
|
3
|
-
|
|
4
|
-
import { createClient } from '@wral/sdk-auth';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
refreshToken,
|
|
9
|
-
logout,
|
|
10
|
-
} from './util.mjs';
|
|
11
|
-
|
|
12
|
-
describe('Authentication Functions', () => {
|
|
13
|
-
let sandbox, fakeStudio, clientStub//, sessionStore;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
sandbox = sinon.createSandbox();
|
|
17
|
-
fakeStudio = {
|
|
18
|
-
pub: sandbox.stub(),
|
|
19
|
-
sub: sandbox.stub(),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// Mocking the client and sessionStorage
|
|
23
|
-
clientStub = {
|
|
24
|
-
mintToken: sandbox.stub(),
|
|
25
|
-
refreshToken: sandbox.stub(),
|
|
26
|
-
};
|
|
27
|
-
sandbox.stub(window, 'sessionStorage').value({
|
|
28
|
-
setItem: sandbox.stub(),
|
|
29
|
-
removeItem: sandbox.stub(),
|
|
30
|
-
getItem: sandbox.stub(),
|
|
31
|
-
});
|
|
32
|
-
sandbox.stub(createClient, 'call').returns(clientStub);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
sandbox.restore();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('refreshToken', () => {
|
|
40
|
-
it('should refresh the token and update session storage', async () => {
|
|
41
|
-
const originalToken = 'expiredToken123';
|
|
42
|
-
const newToken = 'newToken123';
|
|
43
|
-
const config = {
|
|
44
|
-
apibaseurl: 'https://api.example.com/auth',
|
|
45
|
-
};
|
|
46
|
-
clientStub.refreshToken.resolves({ token: newToken });
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const result = await refreshToken({
|
|
50
|
-
studio: fakeStudio,
|
|
51
|
-
config,
|
|
52
|
-
token: originalToken,
|
|
53
|
-
});
|
|
54
|
-
expect(result).to.equal(newToken);
|
|
55
|
-
} catch(error) {
|
|
56
|
-
expect(error.message).to.equal('Failed to fetch');
|
|
57
|
-
}
|
|
58
|
-
/*
|
|
59
|
-
expect(window.sessionStorage.setItem).to.have.been
|
|
60
|
-
.calledWith(tokenStorageKey({ studio: fakeStudio }), newToken);
|
|
61
|
-
*/
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('logout', () => {
|
|
66
|
-
it('should remove the token from session storage', async () => {
|
|
67
|
-
await logout({ studio: fakeStudio });
|
|
68
|
-
/*
|
|
69
|
-
expect(window.sessionStorage.removeItem).to.have.been
|
|
70
|
-
.calledWith(tokenStorageKey({ studio: fakeStudio }));
|
|
71
|
-
*/
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
import { decodeToken } from './util.mjs';
|
|
79
|
-
describe('Token Functions', () => {
|
|
80
|
-
|
|
81
|
-
describe('decodeToken', () => {
|
|
82
|
-
let sandbox;
|
|
83
|
-
|
|
84
|
-
beforeEach(() => {
|
|
85
|
-
// Create a sandbox instance
|
|
86
|
-
sandbox = sinon.createSandbox();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
afterEach(() => {
|
|
90
|
-
// Restore all stubs
|
|
91
|
-
sandbox.restore();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should decode a JWT token and return its payload', () => {
|
|
95
|
-
// Stub window.atob within the sandbox
|
|
96
|
-
sandbox.stub(window, 'atob')
|
|
97
|
-
.callsFake(() => '{"exp":2000000000,"sub":"1234567890"}');
|
|
98
|
-
|
|
99
|
-
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
|
|
100
|
-
+ '.eyJleHAiOjIwMDAwMDAwMDAsInN1YiI6IjEyMzQ1Njc4OTAifQ'
|
|
101
|
-
+ '.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
|
|
102
|
-
const payload = decodeToken(token);
|
|
103
|
-
expect(payload).to.deep.equal({ exp: 2000000000, sub: "1234567890" });
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
import { messageFromError } from './util.mjs';
|
|
112
|
-
describe('messageFromError Function', () => {
|
|
113
|
-
it('should return a user-friendly message for 401 status', () => {
|
|
114
|
-
const error = new Error('Unauthorized');
|
|
115
|
-
error.status = 401;
|
|
116
|
-
expect(messageFromError(error)).to
|
|
117
|
-
.equal('The username or password provided is incorrect.');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should return a user-friendly message for 403 status', () => {
|
|
121
|
-
const error = new Error('Forbidden');
|
|
122
|
-
error.status = 403;
|
|
123
|
-
expect(messageFromError(error)).to
|
|
124
|
-
.equal('You do not have permission to perform this action.');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should return a user-friendly message for 404 status', () => {
|
|
128
|
-
const error = new Error('Not Found');
|
|
129
|
-
error.status = 404;
|
|
130
|
-
expect(messageFromError(error)).to
|
|
131
|
-
.equal('The requested resource could not be found.');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should return a user-friendly message for 500 status', () => {
|
|
135
|
-
const error = new Error('Server Error');
|
|
136
|
-
error.status = 500;
|
|
137
|
-
expect(messageFromError(error)).to
|
|
138
|
-
.equal('An internal server error has occurred.');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should return a user-friendly message for 503 status', () => {
|
|
142
|
-
const error = new Error('Service Unavailable');
|
|
143
|
-
error.status = 503;
|
|
144
|
-
expect(messageFromError(error)).to
|
|
145
|
-
.equal('The server is temporarily unavailable. Please try again later.');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should return a user-friendly message for 504 status', () => {
|
|
149
|
-
const error = new Error('Gateway Timeout');
|
|
150
|
-
error.status = 504;
|
|
151
|
-
expect(messageFromError(error)).to
|
|
152
|
-
.equal('The server took too long to respond. Please try again later.');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should return a user-friendly message for status 0 (network error)', () => {
|
|
156
|
-
const error = new Error('Network Error');
|
|
157
|
-
error.status = 0;
|
|
158
|
-
expect(messageFromError(error)).to
|
|
159
|
-
.equal('The server could not be reached. Please try again later.');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should return the default message if an unknown status is provided', () => {
|
|
163
|
-
const error = new Error('Unexpected Error');
|
|
164
|
-
expect(messageFromError(error)).to.equal('Unexpected Error');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should return "An unknown error has occurred." if no message is provided', () => {
|
|
168
|
-
const error = new Error();
|
|
169
|
-
expect(messageFromError(error)).to.equal('An unknown error has occurred.');
|
|
170
|
-
});
|
|
171
|
-
});
|
package/vite.config.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { playwrightLauncher } from '@web/test-runner-playwright';
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
files: 'src/**/*.test.mjs', // Path to your test files
|
|
5
|
-
browsers: [
|
|
6
|
-
playwrightLauncher({ product: 'chromium' }), // You can add 'firefox' and 'webkit'
|
|
7
|
-
],
|
|
8
|
-
nodeResolve: {
|
|
9
|
-
exportConditions: ['production'],
|
|
10
|
-
},
|
|
11
|
-
coverage: true,
|
|
12
|
-
coverageConfig: {
|
|
13
|
-
include: ['src/**/*.mjs'], // Specify which files to include in coverage
|
|
14
|
-
// Exclude test files and node_modules
|
|
15
|
-
exclude: ['**/*.test.mjs', '**/node_modules/**'],
|
|
16
|
-
threshold: {
|
|
17
|
-
global: {
|
|
18
|
-
statements: 70,
|
|
19
|
-
branches: 70,
|
|
20
|
-
functions: 70,
|
|
21
|
-
lines: 70,
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
report: true,
|
|
25
|
-
reportDir: '.coverage', // Directory where coverage reports will be placed
|
|
26
|
-
reporter: ['text', 'html'], // Coverage reporters to use
|
|
27
|
-
},
|
|
28
|
-
};
|