@nsshunt/stsoauth2plugin 0.0.3
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/.eslintrc.json +27 -0
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/npm-publish.yml +54 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/babel.config.json +6 -0
- package/build.sh +29 -0
- package/dist/Utils/CryptoUtils.js +32 -0
- package/dist/Utils/CryptoUtils.js.map +1 -0
- package/dist/Utils/QueryParams.js +49 -0
- package/dist/Utils/QueryParams.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.js +8 -0
- package/dist/index.test.js.map +1 -0
- package/dist/stsStorage.js +152 -0
- package/dist/stsStorage.js.map +1 -0
- package/dist/stsoauth2manager.js +327 -0
- package/dist/stsoauth2manager.js.map +1 -0
- package/dist/stsoauth2types.js +29 -0
- package/dist/stsoauth2types.js.map +1 -0
- package/dist/stsoauth2worker.js +553 -0
- package/dist/stsoauth2worker.js.map +1 -0
- package/package.json +43 -0
- package/src/Utils/CryptoUtils.ts +32 -0
- package/src/Utils/QueryParams.ts +48 -0
- package/src/index.test.ts +10 -0
- package/src/index.ts +3 -0
- package/src/stsStorage.ts +158 -0
- package/src/stsoauth2manager.ts +350 -0
- package/src/stsoauth2types.ts +108 -0
- package/src/stsoauth2worker.ts +542 -0
- package/tsconfig.json +31 -0
- package/types/Utils/CryptoUtils.d.ts +7 -0
- package/types/Utils/CryptoUtils.d.ts.map +1 -0
- package/types/Utils/QueryParams.d.ts +8 -0
- package/types/Utils/QueryParams.d.ts.map +1 -0
- package/types/index.d.ts +3 -0
- package/types/index.d.ts.map +1 -0
- package/types/index.test.d.ts +1 -0
- package/types/index.test.d.ts.map +1 -0
- package/types/stsStorage.d.ts +22 -0
- package/types/stsStorage.d.ts.map +1 -0
- package/types/stsoauth2manager.d.ts +5 -0
- package/types/stsoauth2manager.d.ts.map +1 -0
- package/types/stsoauth2types.d.ts +89 -0
- package/types/stsoauth2types.d.ts.map +1 -0
- package/types/stsoauth2worker.d.ts +2 -0
- package/types/stsoauth2worker.d.ts.map +1 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// https://github.com/auth0/auth0-spa-js/blob/1de6427f81a8c5b005e9b6d10b9efb1e73542528/static/index.html
|
|
2
|
+
// https://stackoverflow.com/questions/12446317/change-url-without-redirecting-using-javascript
|
|
3
|
+
class QueryParams {
|
|
4
|
+
DecodeQueryParams = (params) => {
|
|
5
|
+
const retObj = { };
|
|
6
|
+
const arr = Object.keys(params)
|
|
7
|
+
.filter(k => typeof params[k] !== 'undefined')
|
|
8
|
+
.map(k => {
|
|
9
|
+
retObj[decodeURIComponent(k)] = decodeURIComponent(params[k]);
|
|
10
|
+
});
|
|
11
|
+
return retObj;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
CreateQueryParams = (params) => {
|
|
15
|
+
return Object.keys(params)
|
|
16
|
+
.filter(k => typeof params[k] !== 'undefined')
|
|
17
|
+
.map(k => {
|
|
18
|
+
if (Array.isArray(params[k])) {
|
|
19
|
+
return encodeURIComponent(k) + '=' + encodeURIComponent(params[k].join(' '))
|
|
20
|
+
} else {
|
|
21
|
+
return encodeURIComponent(k) + '=' + encodeURIComponent(params[k])
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
.join('&');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_GetQueryParams = (param) => {
|
|
28
|
+
let retVal = { };
|
|
29
|
+
const uri = param.split("?");
|
|
30
|
+
if (uri.length == 2) {
|
|
31
|
+
const vars = uri[1].split("&");
|
|
32
|
+
const getVars = {};
|
|
33
|
+
let tmp = "";
|
|
34
|
+
vars.forEach(function (v) {
|
|
35
|
+
tmp = v.split("=");
|
|
36
|
+
if (tmp.length == 2) getVars[tmp[0]] = tmp[1];
|
|
37
|
+
});
|
|
38
|
+
retVal = this.DecodeQueryParams(getVars);
|
|
39
|
+
}
|
|
40
|
+
return retVal;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
GetQueryParams = () => {
|
|
44
|
+
return this._GetQueryParams(window.location.href);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default QueryParams;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
const debug = Debug(`proc:${process.pid}:storage.ts`);
|
|
3
|
+
|
|
4
|
+
import * as Cookies from 'es-cookie';
|
|
5
|
+
import { JSONObject } from "@nsshunt/stsutils";
|
|
6
|
+
|
|
7
|
+
export interface IStsStorage<T> {
|
|
8
|
+
get(key: string): T
|
|
9
|
+
set(key: string, value: T, options?: JSONObject): void
|
|
10
|
+
remove(key: string): void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum ClientStorageType {
|
|
14
|
+
LOCAL_STORAGE = 'LocalStorage', //@@ todo
|
|
15
|
+
SESSION_STORAGE = 'SessionStorage',
|
|
16
|
+
COOKIE_STORAGE = 'CookieStorage',
|
|
17
|
+
MEMORY_STORAGE = 'MemoryStorage' //@@ todo
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class CookieStorage<T> implements IStsStorage<T>
|
|
21
|
+
{
|
|
22
|
+
get = (key: string): T => {
|
|
23
|
+
const raw = Cookies.get(key);
|
|
24
|
+
if (raw) {
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
} else {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set = (key: string, value: T, options: JSONObject = { }) => {
|
|
32
|
+
let cookieAttributes: Cookies.CookieAttributes = { };
|
|
33
|
+
if ('https:' === window.location.protocol) {
|
|
34
|
+
cookieAttributes = {
|
|
35
|
+
secure: true,
|
|
36
|
+
sameSite: 'none'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (options && options.daysUntilExpire) {
|
|
41
|
+
cookieAttributes.expires = options.daysUntilExpire;
|
|
42
|
+
} else {
|
|
43
|
+
cookieAttributes.expires = 1;
|
|
44
|
+
}
|
|
45
|
+
debug(`CookieStorage.set: key: ${key}, value: [${value}]`);
|
|
46
|
+
Cookies.set(key, JSON.stringify(value), cookieAttributes);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
remove = (key: string): void => {
|
|
50
|
+
Cookies.remove(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class SessionStorage<T> implements IStsStorage<T>
|
|
55
|
+
{
|
|
56
|
+
get = (key: string): T => {
|
|
57
|
+
const value: string = sessionStorage.getItem(key);
|
|
58
|
+
if (typeof value === 'undefined') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (value === null) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return JSON.parse(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
set = (key: string, value: T): void => {
|
|
68
|
+
debug(`SessionStorage.set: key: ${key}, value: [${value}]`);
|
|
69
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
remove = (key: string): void => {
|
|
73
|
+
sessionStorage.removeItem(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class LocalStorage<T> implements IStsStorage<T>
|
|
78
|
+
{
|
|
79
|
+
get = (key: string): T => {
|
|
80
|
+
const value: string = localStorage.getItem(key);
|
|
81
|
+
if (typeof value === 'undefined') {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (value === null) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return JSON.parse(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set = (key: string, value: T): void => {
|
|
91
|
+
debug(`LocalStorage.set: key: ${key}, value: [${value}]`);
|
|
92
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
remove = (key: string): void => {
|
|
96
|
+
localStorage.removeItem(key);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class MemoryStorage<T> implements IStsStorage<T>
|
|
101
|
+
{
|
|
102
|
+
#store: Record<string, T> = { };
|
|
103
|
+
|
|
104
|
+
get = (key: string): T => {
|
|
105
|
+
const value: T = this.#store[key];
|
|
106
|
+
if (typeof value === 'undefined') {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (value === null) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
set = (key: string, value: T): void => {
|
|
116
|
+
debug(`MemoryStorage.set: key: ${key}, value: [${value}]`);
|
|
117
|
+
this.#store[key] = value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
remove = (key: string): void => {
|
|
121
|
+
delete this.#store[key];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class ClientStorageOptions {
|
|
126
|
+
clientStorageType: ClientStorageType = ClientStorageType.MEMORY_STORAGE;
|
|
127
|
+
storageOptions?: JSONObject
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class ClientStorageFactory<T>
|
|
131
|
+
{
|
|
132
|
+
#storage = null;
|
|
133
|
+
|
|
134
|
+
constructor(options: ClientStorageOptions) {
|
|
135
|
+
switch (options.clientStorageType) {
|
|
136
|
+
case ClientStorageType.SESSION_STORAGE :
|
|
137
|
+
this.#storage = new SessionStorage<T>();
|
|
138
|
+
break;
|
|
139
|
+
case ClientStorageType.LOCAL_STORAGE :
|
|
140
|
+
this.#storage = new LocalStorage<T>();
|
|
141
|
+
break;
|
|
142
|
+
case ClientStorageType.COOKIE_STORAGE :
|
|
143
|
+
this.#storage = new CookieStorage<T>();
|
|
144
|
+
break;
|
|
145
|
+
case ClientStorageType.MEMORY_STORAGE :
|
|
146
|
+
this.#storage = new MemoryStorage<T>();
|
|
147
|
+
break;
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(`Unknown [${options.clientStorageType}] storage type.`);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
GetStorage(): IStsStorage<T>
|
|
155
|
+
{
|
|
156
|
+
return this.#storage;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
const debug = Debug(`proc:${process.pid}:stsoauth2manager.ts`);
|
|
3
|
+
|
|
4
|
+
import { JSONObject, OAuth2ParameterType } from '@nsshunt/stsutils';
|
|
5
|
+
|
|
6
|
+
import CryptoUtils from './Utils/CryptoUtils'
|
|
7
|
+
import QueryParams from './Utils/QueryParams';
|
|
8
|
+
|
|
9
|
+
import { IAuthorizeOptions, IAuthorizeResponse, IAuthorizeErrorResponse, AuthenticateEvent,
|
|
10
|
+
ISTSOAuth2ManagerOptions, IOauth2ListenerMessage, IOauth2ListenerMessageResponse, IOauth2ListenerCommand } from './stsoauth2types'
|
|
11
|
+
|
|
12
|
+
import { Router } from 'vue-router' //@@ only need the type
|
|
13
|
+
|
|
14
|
+
import { IStsStorage, ClientStorageType, ClientStorageFactory } from './stsStorage'
|
|
15
|
+
|
|
16
|
+
//import createPersistedState from "vuex-persistedstate"; // https://www.npmjs.com/package/vuex-persistedstate
|
|
17
|
+
import jwt_decode from "jwt-decode";
|
|
18
|
+
//import { transformWithEsbuild } from "vite";
|
|
19
|
+
|
|
20
|
+
// STS Client SDK for SPAs
|
|
21
|
+
class STSOAuth2Manager {
|
|
22
|
+
#storageManager = null;
|
|
23
|
+
#router: Router = null;
|
|
24
|
+
#store = null;
|
|
25
|
+
#cUtils = new CryptoUtils();
|
|
26
|
+
#qParams = new QueryParams();
|
|
27
|
+
#STORAGE_AUTHORIZE_OPTIONS_KEY = 'authorize_options.stsmda.com.au';
|
|
28
|
+
#STORAGE_SESSION_KEY = 'session.stsmda.com.au';
|
|
29
|
+
#aic = null;
|
|
30
|
+
#options: ISTSOAuth2ManagerOptions = null;
|
|
31
|
+
#messages: Record<number, IOauth2ListenerMessage> = { };
|
|
32
|
+
#oauth2ManagerPort: MessagePort;
|
|
33
|
+
#messageId = 0;
|
|
34
|
+
#messageHandlers: Record<number, any> = { }; // keyed by messageId
|
|
35
|
+
#messageTimeout = 1000;
|
|
36
|
+
#worker: Worker = null;
|
|
37
|
+
#transactionStore: IStsStorage<IAuthorizeOptions> = null; // Transient transaction data used to establish a session via OAuth2 authorize handshake
|
|
38
|
+
|
|
39
|
+
constructor(app, options: ISTSOAuth2ManagerOptions) {
|
|
40
|
+
this.#options = options;
|
|
41
|
+
this.#storageManager = app.config.globalProperties.$sts.storage;
|
|
42
|
+
this.#store = app.config.globalProperties.$store;
|
|
43
|
+
this.#aic = app.config.globalProperties.$sts.aic.PrimaryPublishInstrumentController;
|
|
44
|
+
this.#router = app.config.globalProperties.$router;
|
|
45
|
+
|
|
46
|
+
// Use session storage for the transient nature of the OAuth2 authorize handshake. Once completed, the storage will be removed.
|
|
47
|
+
this.#transactionStore = new ClientStorageFactory<IAuthorizeOptions>({clientStorageType: ClientStorageType.SESSION_STORAGE}).GetStorage();
|
|
48
|
+
|
|
49
|
+
this.#worker = new Worker(new URL('./stsoauth2worker.ts', import.meta.url), {
|
|
50
|
+
type: 'module'
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.#worker.onmessage = (data: MessageEvent) => {
|
|
54
|
+
console.log(`this.#worker.onmessage = [${data}]`.green);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
this.#worker.onerror = function(error) {
|
|
58
|
+
console.log(`this.#worker.onerror = [${error}]`.green);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
port1: oauth2ManagerPort, // process message port
|
|
63
|
+
port2: oauth2WorkerPort // collector message port
|
|
64
|
+
} = new MessageChannel();
|
|
65
|
+
this.#oauth2ManagerPort = oauth2ManagerPort;
|
|
66
|
+
|
|
67
|
+
this.#worker.postMessage(oauth2WorkerPort, [ oauth2WorkerPort ]);
|
|
68
|
+
|
|
69
|
+
this.#oauth2ManagerPort.onmessage = (data: MessageEvent) => {
|
|
70
|
+
this.#ProcessMessageResponse(data);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
this.#SetupStoreNamespace();
|
|
75
|
+
this.#SetupRoute(app, options.router);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#ProcessMessageResponse = (data: MessageEvent) => {
|
|
79
|
+
const messageResponse: IOauth2ListenerMessageResponse = data.data as IOauth2ListenerMessageResponse;
|
|
80
|
+
if (messageResponse.messageId === -1) {
|
|
81
|
+
// unsolicted message
|
|
82
|
+
switch (messageResponse.command) {
|
|
83
|
+
case IOauth2ListenerCommand.AUTHENTICATE_EVENT :
|
|
84
|
+
this.#HandleAuthenticateEvent(messageResponse.payload as string);
|
|
85
|
+
break;
|
|
86
|
+
case IOauth2ListenerCommand.ERROR :
|
|
87
|
+
this.#HandleErrorEvent(messageResponse.payload as JSONObject);
|
|
88
|
+
break;
|
|
89
|
+
default :
|
|
90
|
+
throw new Error(`ProcessMessageResponse command [${messageResponse.command}] not valid.`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
const callBack = this.#messageHandlers[messageResponse.messageId];
|
|
94
|
+
if (callBack) {
|
|
95
|
+
callBack(messageResponse);
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error(`Message: [${messageResponse.messageId}] does not exists in callBacks.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#PostMessage = (message: IOauth2ListenerMessage): Promise<IOauth2ListenerMessageResponse> => {
|
|
103
|
+
message.messageId = this.#messageId++;
|
|
104
|
+
|
|
105
|
+
return new Promise<IOauth2ListenerMessageResponse>((resolve, reject) => {
|
|
106
|
+
// Setup message timeout
|
|
107
|
+
const timeout: NodeJS.Timeout = setTimeout(() => {
|
|
108
|
+
delete this.#messageHandlers[message.messageId];
|
|
109
|
+
reject(`Message: [${message.messageId}] timeout error after: [${this.#messageTimeout}] ms.`);
|
|
110
|
+
}, this.#messageTimeout);
|
|
111
|
+
|
|
112
|
+
// Setup message callback based on messageId
|
|
113
|
+
this.#messageHandlers[message.messageId] = (response: IOauth2ListenerMessageResponse) => {
|
|
114
|
+
clearTimeout(timeout);
|
|
115
|
+
delete this.#messageHandlers[message.messageId];
|
|
116
|
+
resolve(response);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Send the message
|
|
120
|
+
this.#oauth2ManagerPort.postMessage(message);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Will come from message channel
|
|
125
|
+
#HandleErrorEvent = (error: JSONObject): void => {
|
|
126
|
+
this.#store.commit('AuthorizeError', {
|
|
127
|
+
message: error
|
|
128
|
+
});
|
|
129
|
+
// plugin to do this ...
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
this.#router.replace('/error'); //@@ was push
|
|
132
|
+
}, 0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#HandleAuthenticateEvent: AuthenticateEvent = (id_token: string): void => {
|
|
136
|
+
if (this.#options.authenticateEvent) {
|
|
137
|
+
this.#options.authenticateEvent(id_token);
|
|
138
|
+
}
|
|
139
|
+
this.#store.commit('stsOAuth2SDK/SessionData', id_token);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#SetupRoute = (app, router) => {
|
|
143
|
+
router.beforeEach(async (to, from) => {
|
|
144
|
+
const store = app.config.globalProperties.$store;
|
|
145
|
+
const sts = app.config.globalProperties.$sts;
|
|
146
|
+
|
|
147
|
+
debug(`beforeEach: from: [${from.path}], to: [${to.path}]`.gray);
|
|
148
|
+
if (store.getters['stsOAuth2SDK/LoggedIn'] === false) {
|
|
149
|
+
console.log(`Not logged in`);
|
|
150
|
+
// Not logged in
|
|
151
|
+
if (to.path.localeCompare('/authorize') === 0) {
|
|
152
|
+
console.log(`to = /authorize`);
|
|
153
|
+
return true;
|
|
154
|
+
} else if (to.path.localeCompare('/consent') === 0) {
|
|
155
|
+
// Need to check if we are in the correct state, if not - drop back to the start of the process
|
|
156
|
+
if (typeof store.getters.Session.sessionId !== 'undefined') {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (to.path.localeCompare('/logout') === 0) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (to.path.localeCompare('/error') === 0) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (to.path.localeCompare('/logout') === 0) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const str = to.query;
|
|
171
|
+
// Check if this route is from a redirect from the authorization server
|
|
172
|
+
if (str[OAuth2ParameterType.CODE] || str[OAuth2ParameterType.ERROR]) {
|
|
173
|
+
|
|
174
|
+
console.log(`#SetupRout:str = [${str}]`);
|
|
175
|
+
|
|
176
|
+
const retVal: boolean = await sts.om.HandleRedirect(str);
|
|
177
|
+
if (retVal) {
|
|
178
|
+
// Success
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
window.history.replaceState(
|
|
181
|
+
{},
|
|
182
|
+
document.title,
|
|
183
|
+
window.location.origin + '/');
|
|
184
|
+
}, 0);
|
|
185
|
+
return true;
|
|
186
|
+
} else {
|
|
187
|
+
// Error
|
|
188
|
+
//@@ need the error data here - or use the vuex store ?
|
|
189
|
+
this.#router.replace('/error'); //@@ was push
|
|
190
|
+
|
|
191
|
+
//@@ should replaceState be used as in above?
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const sessionRestored = await sts.om.RestoreSession();
|
|
197
|
+
console.log(`#SetupRoute:sessionRestored [${sessionRestored}]`);
|
|
198
|
+
|
|
199
|
+
if (sessionRestored !== true) {
|
|
200
|
+
console.log('Session not restored - Need to Authorize');
|
|
201
|
+
sts.om.Authorize();
|
|
202
|
+
return false;
|
|
203
|
+
} else {
|
|
204
|
+
return '/';
|
|
205
|
+
//router.replace({ path: '/' })
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// Prevent pages if already logged in
|
|
209
|
+
if (to.path.localeCompare('/consent') === 0) {
|
|
210
|
+
return '/';
|
|
211
|
+
/*
|
|
212
|
+
router.replace({ path: '/' })
|
|
213
|
+
return false;
|
|
214
|
+
*/
|
|
215
|
+
}
|
|
216
|
+
if (to.path.localeCompare('/authorize') === 0) {
|
|
217
|
+
router.replace({ path: '/' })
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (to.path.localeCompare('/logout') === 0) {
|
|
221
|
+
router.replace({ path: '/' })
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
if (to.path.localeCompare('/') === 0) {
|
|
228
|
+
// In case press the back button in the browser shows previous query string params, replace them ...
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
window.history.replaceState(
|
|
231
|
+
{},
|
|
232
|
+
document.title,
|
|
233
|
+
window.location.origin + '/');
|
|
234
|
+
}, 0);
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
*/
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#SetupStoreNamespace = () => {
|
|
243
|
+
this.#store.registerModule('stsOAuth2SDK', {
|
|
244
|
+
namespaced: true,
|
|
245
|
+
|
|
246
|
+
state () {
|
|
247
|
+
return {
|
|
248
|
+
// STS Client SDK options. These are parameters initiated by the client SPA and used for the end-to-end transaction processing.
|
|
249
|
+
//authorizeOptions: { },
|
|
250
|
+
|
|
251
|
+
sessionData: { },
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
getters: {
|
|
256
|
+
SessionData (state) {
|
|
257
|
+
return state.sessionData;
|
|
258
|
+
},
|
|
259
|
+
LoggedIn (state) {
|
|
260
|
+
if (typeof state.sessionData === 'undefined') {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (state.sessionData === null) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
},
|
|
268
|
+
UserDetails (state) {
|
|
269
|
+
//if (state.sessionData && state.sessionData.id_token) {
|
|
270
|
+
if (state.sessionData) {
|
|
271
|
+
const id_token = state.sessionData;
|
|
272
|
+
const decodedIdToken = jwt_decode(id_token);
|
|
273
|
+
return decodedIdToken;
|
|
274
|
+
} else {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
mutations: {
|
|
281
|
+
SessionData (state, sessionData) {
|
|
282
|
+
state.sessionData = sessionData;
|
|
283
|
+
console.log(`commit [sessionData]: ${JSON.stringify(sessionData)}`)
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
}, { preserveState: true });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
RestoreSession = async(): Promise<boolean> => {
|
|
290
|
+
try {
|
|
291
|
+
const response: IOauth2ListenerMessageResponse = await this.#PostMessage({ command: IOauth2ListenerCommand.RESTORE_SESSION });
|
|
292
|
+
return response.payload;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.log(`RestoreSession Error: ${error}`.red);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
Authorize = async (): Promise<void> => {
|
|
300
|
+
try {
|
|
301
|
+
const response: IOauth2ListenerMessageResponse = await this.#PostMessage({ command: IOauth2ListenerCommand.AUTHORIZE });
|
|
302
|
+
this.#transactionStore.set(this.#STORAGE_AUTHORIZE_OPTIONS_KEY, response.payload.authorizeOptions);
|
|
303
|
+
const url = response.payload.url;
|
|
304
|
+
window.location.replace(url);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.log(`Authorize Error: ${error}`.red);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
HandleRedirect = async (queryVars: JSONObject): Promise<boolean> => {
|
|
311
|
+
try {
|
|
312
|
+
let response: IOauth2ListenerMessageResponse = null;
|
|
313
|
+
if (queryVars[OAuth2ParameterType.CODE]) {
|
|
314
|
+
|
|
315
|
+
const authorizeOptions: IAuthorizeOptions = this.#transactionStore.get(this.#STORAGE_AUTHORIZE_OPTIONS_KEY) as IAuthorizeOptions;
|
|
316
|
+
this.#transactionStore.remove(this.#STORAGE_AUTHORIZE_OPTIONS_KEY);
|
|
317
|
+
|
|
318
|
+
response = await this.#PostMessage({ command: IOauth2ListenerCommand.HANDLE_REDIRECT, payload: {
|
|
319
|
+
queryVars: queryVars as IAuthorizeResponse,
|
|
320
|
+
authorizeOptions
|
|
321
|
+
}});
|
|
322
|
+
} else {
|
|
323
|
+
response = await this.#PostMessage({ command: IOauth2ListenerCommand.HANDLE_REDIRECT, payload: queryVars as IAuthorizeErrorResponse });
|
|
324
|
+
}
|
|
325
|
+
return response.payload;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.log(`HandleRedirect Error: ${error}`.red);
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
Logout = async (): Promise<boolean> => {
|
|
333
|
+
try {
|
|
334
|
+
const response: IOauth2ListenerMessageResponse = await this.#PostMessage({ command: IOauth2ListenerCommand.LOGOUT });
|
|
335
|
+
return response.payload;
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.log(`Logout Error: ${error}`.red);
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const STSOAuth2ManagerPlugin = {
|
|
344
|
+
install: (app, router) => {
|
|
345
|
+
const om = new STSOAuth2Manager(app, router);
|
|
346
|
+
app.config.globalProperties.$sts.om = om;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export default STSOAuth2ManagerPlugin;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Router } from 'vue-router'
|
|
2
|
+
|
|
3
|
+
export enum AuthorizeOptionsResponseType {
|
|
4
|
+
CODE = 'code',
|
|
5
|
+
ID_TOKEN = 'id_token',
|
|
6
|
+
TOKEN = 'token'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum AuthorizeOptionsResponseMode {
|
|
10
|
+
QUERY = 'query',
|
|
11
|
+
FRAGMENT = 'fragment',
|
|
12
|
+
FORM_POST = 'form_post'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
|
16
|
+
export interface IAuthorizeOptions {
|
|
17
|
+
client_id: string,
|
|
18
|
+
nonce: string,
|
|
19
|
+
response_type: AuthorizeOptionsResponseType[], // Must include: 'code' and may include: 'id_token' and/or 'token'
|
|
20
|
+
redirect_uri: string,
|
|
21
|
+
response_mode: AuthorizeOptionsResponseMode
|
|
22
|
+
scope: string, // A space-separated list of scopes that you want the user to consent to. For the /authorize leg of the request, this parameter can cover multiple resources. This value allows your app to get consent for multiple web APIs you want to call.
|
|
23
|
+
state: string, // Client application state (if required)
|
|
24
|
+
code_challenge: string,
|
|
25
|
+
code_challenge_method: string, //@@ enum S256
|
|
26
|
+
code_verifier?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IAuthorizeResponse {
|
|
30
|
+
state: string, // state passed to authorize end-point
|
|
31
|
+
code: string // authorization code
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface IAuthorizeErrorResponse {
|
|
35
|
+
error: string,
|
|
36
|
+
error_description: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export enum OAuthGrantTypes {
|
|
40
|
+
CLIENT_CREDENTIALS = 'client_credentials',
|
|
41
|
+
AUTHORIZATION_CODE = 'authorization_code',
|
|
42
|
+
REFRESH_TOKEN = 'refresh_token'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface IAuthorizationCodeFlowParameters {
|
|
46
|
+
client_id: string,
|
|
47
|
+
scope: string,
|
|
48
|
+
code: string,
|
|
49
|
+
redirect_uri: string, // URI
|
|
50
|
+
grant_type: OAuthGrantTypes, // 'authorization_code', //@@ need enum
|
|
51
|
+
code_verifier: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface IRefreshFlowParameters {
|
|
55
|
+
client_id: string,
|
|
56
|
+
scope: string,
|
|
57
|
+
refresh_token: string, // JWT
|
|
58
|
+
grant_type: OAuthGrantTypes // 'refresh_token' //@@ enum
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ITokenResponse {
|
|
62
|
+
access_token: string, // JWT
|
|
63
|
+
token_type: string, //@@ "Bearer" only
|
|
64
|
+
expires_in: number,
|
|
65
|
+
scope: string,
|
|
66
|
+
refresh_token: string, // JWT
|
|
67
|
+
id_token: string, // JWT
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ITokenErrorResponse {
|
|
71
|
+
error: string,
|
|
72
|
+
error_description: string,
|
|
73
|
+
error_codes: number[],
|
|
74
|
+
timestamp: number
|
|
75
|
+
details: unknown //@@ STS attribute ?
|
|
76
|
+
//"trace_id": "255d1aef-8c98-452f-ac51-23d051240864", //@@ MS attribute
|
|
77
|
+
//"correlation_id": "fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7" //@@ MS attribute
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type AuthenticateEvent = (id_token: string) => void;
|
|
81
|
+
|
|
82
|
+
// ---------------
|
|
83
|
+
|
|
84
|
+
export enum IOauth2ListenerCommand {
|
|
85
|
+
RESTORE_SESSION = 'RestoreSession',
|
|
86
|
+
AUTHORIZE = 'Authorize',
|
|
87
|
+
HANDLE_REDIRECT = 'HandleRedirect',
|
|
88
|
+
LOGOUT = 'Logout',
|
|
89
|
+
AUTHENTICATE_EVENT = 'AuthenticateEvent',
|
|
90
|
+
ERROR = 'Error'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface IOauth2ListenerMessage {
|
|
94
|
+
messageId?: number
|
|
95
|
+
command: IOauth2ListenerCommand
|
|
96
|
+
payload?: any
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface IOauth2ListenerMessageResponse {
|
|
100
|
+
messageId: number
|
|
101
|
+
command: IOauth2ListenerCommand
|
|
102
|
+
payload: any
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ISTSOAuth2ManagerOptions {
|
|
106
|
+
router: Router
|
|
107
|
+
authenticateEvent?: AuthenticateEvent
|
|
108
|
+
}
|