@api-client/core 0.5.20 → 0.5.21
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/build/browser.d.ts +7 -0
- package/build/browser.js +10 -0
- package/build/browser.js.map +1 -1
- package/build/index.d.ts +4 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -1
- package/build/src/authorization/AuthorizationError.d.ts +23 -0
- package/build/src/authorization/AuthorizationError.js +33 -0
- package/build/src/authorization/AuthorizationError.js.map +1 -0
- package/build/src/authorization/CustomParameters.d.ts +24 -0
- package/build/src/authorization/CustomParameters.js +59 -0
- package/build/src/authorization/CustomParameters.js.map +1 -0
- package/build/src/authorization/OAuth2Authorization.d.ts +332 -0
- package/build/src/authorization/OAuth2Authorization.js +965 -0
- package/build/src/authorization/OAuth2Authorization.js.map +1 -0
- package/build/src/authorization/OidcAuthorization.d.ts +34 -0
- package/build/src/authorization/OidcAuthorization.js +139 -0
- package/build/src/authorization/OidcAuthorization.js.map +1 -0
- package/build/src/authorization/Utils.d.ts +51 -0
- package/build/src/authorization/Utils.js +122 -0
- package/build/src/authorization/Utils.js.map +1 -0
- package/build/src/authorization/lib/IframeAuthorization.d.ts +53 -0
- package/build/src/authorization/lib/IframeAuthorization.js +116 -0
- package/build/src/authorization/lib/IframeAuthorization.js.map +1 -0
- package/build/src/authorization/lib/KnownGrants.d.ts +6 -0
- package/build/src/authorization/lib/KnownGrants.js +7 -0
- package/build/src/authorization/lib/KnownGrants.js.map +1 -0
- package/build/src/authorization/lib/PopupAuthorization.d.ts +41 -0
- package/build/src/authorization/lib/PopupAuthorization.js +73 -0
- package/build/src/authorization/lib/PopupAuthorization.js.map +1 -0
- package/build/src/authorization/lib/Tokens.d.ts +55 -0
- package/build/src/authorization/lib/Tokens.js +117 -0
- package/build/src/authorization/lib/Tokens.js.map +1 -0
- package/build/src/authorization/types.d.ts +174 -0
- package/build/src/authorization/types.js +2 -0
- package/build/src/authorization/types.js.map +1 -0
- package/oauth-popup.html +29 -0
- package/package.json +3 -1
- package/src/authorization/AuthorizationError.ts +25 -0
- package/src/authorization/CustomParameters.ts +61 -0
- package/src/authorization/OAuth2Authorization.ts +1027 -0
- package/src/authorization/OidcAuthorization.ts +143 -0
- package/src/authorization/Utils.ts +126 -0
- package/src/authorization/lib/IframeAuthorization.ts +128 -0
- package/src/authorization/lib/KnownGrants.ts +6 -0
- package/src/authorization/lib/PopupAuthorization.ts +80 -0
- package/src/authorization/lib/Tokens.ts +124 -0
- package/src/authorization/types.ts +176 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { IOidcTokenInfo, IOidcTokenError, ITokenInfo } from '../models/Authorization.js';
|
|
2
|
+
import { Tokens } from './lib/Tokens.js';
|
|
3
|
+
import { OAuth2Authorization, grantResponseMapping, reportOAuthError, resolveFunction, rejectFunction, handleTokenInfo } from './OAuth2Authorization.js';
|
|
4
|
+
import { nonceGenerator } from './Utils.js';
|
|
5
|
+
|
|
6
|
+
export class OidcAuthorization extends OAuth2Authorization {
|
|
7
|
+
/**
|
|
8
|
+
* @returns The parameters to build popup URL.
|
|
9
|
+
*/
|
|
10
|
+
async buildPopupUrlParams(): Promise<URL | null> {
|
|
11
|
+
const url = await super.buildPopupUrlParams();
|
|
12
|
+
if (url === null) {
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
15
|
+
const type = (this.settings.responseType || grantResponseMapping[this.settings.grantType!]);
|
|
16
|
+
// ID token nonce
|
|
17
|
+
if (type.includes('id_token')) {
|
|
18
|
+
url.searchParams.set('nonce', nonceGenerator());
|
|
19
|
+
}
|
|
20
|
+
return url;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param params The instance of search params with the response from the auth dialog.
|
|
25
|
+
* @returns true when the params qualify as an authorization popup redirect response.
|
|
26
|
+
*/
|
|
27
|
+
validateTokenResponse(params: URLSearchParams): boolean {
|
|
28
|
+
if (params.has('id_token')) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return super.validateTokenResponse(params);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Processes the response returned by the popup or the iframe.
|
|
36
|
+
*/
|
|
37
|
+
async processTokenResponse(params: URLSearchParams): Promise<void> {
|
|
38
|
+
this.clearObservers();
|
|
39
|
+
const state = params.get('state');
|
|
40
|
+
if (!state) {
|
|
41
|
+
this[reportOAuthError]('Server did not return the state parameter.', 'no_state');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (state !== this.state) {
|
|
45
|
+
// The authorization class (this) is created per token request so this can only have one state.
|
|
46
|
+
// When the app requests for more tokens at the same time is should create multiple instances of this.
|
|
47
|
+
this[reportOAuthError]('The state value returned by the authorization server is invalid.', 'invalid_state');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (params.has('error')) {
|
|
51
|
+
const info = this.createTokenResponseError(params);
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
this[reportOAuthError](...info);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// this is the time when the tokens are received. +- a few ms.
|
|
57
|
+
const time = Date.now();
|
|
58
|
+
const tokens: (IOidcTokenInfo|IOidcTokenError)[] | null = this.prepareTokens(params, time);
|
|
59
|
+
if (!Array.isArray(tokens) || !tokens.length) {
|
|
60
|
+
this[reportOAuthError]('The authorization response has unknown response type configuration.', 'unknown_state');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const codeIndex = tokens.findIndex(i => i.responseType === 'code');
|
|
64
|
+
if (codeIndex >= 0) {
|
|
65
|
+
const codeToken = tokens[codeIndex] as IOidcTokenInfo;
|
|
66
|
+
try {
|
|
67
|
+
const info = await this.getCodeInfo(codeToken.code!);
|
|
68
|
+
if (info.error) {
|
|
69
|
+
tokens[codeIndex] = {
|
|
70
|
+
responseType: codeToken.responseType,
|
|
71
|
+
state: codeToken.state,
|
|
72
|
+
error: info.error,
|
|
73
|
+
errorDescription: info.errorDescription,
|
|
74
|
+
} as IOidcTokenError;
|
|
75
|
+
} else {
|
|
76
|
+
codeToken.accessToken = info.accessToken;
|
|
77
|
+
codeToken.refreshToken = info.refreshToken;
|
|
78
|
+
codeToken.idToken = info.idToken;
|
|
79
|
+
codeToken.tokenType = info.tokenType;
|
|
80
|
+
codeToken.expiresIn = info.expiresIn;
|
|
81
|
+
codeToken.scope = Tokens.computeTokenInfoScopes(this.settings.scopes, info.scope);
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
tokens[codeIndex] = {
|
|
85
|
+
responseType: codeToken.responseType,
|
|
86
|
+
state: codeToken.state,
|
|
87
|
+
error: 'unknown_state',
|
|
88
|
+
errorDescription: (e as Error).message,
|
|
89
|
+
} as IOidcTokenError;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.finish(tokens);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates a token info object for each requested response type. These are created from the params received from the
|
|
97
|
+
* redirect URI. This means that it might not be complete (for code response type).
|
|
98
|
+
* @param params
|
|
99
|
+
* @param time Timestamp when the tokens were created
|
|
100
|
+
*/
|
|
101
|
+
prepareTokens(params: URLSearchParams, time: number): IOidcTokenInfo[] | null {
|
|
102
|
+
const { grantType, responseType='', scopes } = this.settings;
|
|
103
|
+
let type = responseType;
|
|
104
|
+
if (!type) {
|
|
105
|
+
type = grantResponseMapping[grantType!];
|
|
106
|
+
}
|
|
107
|
+
if (!type) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const types = type.split(' ').map(i => i.trim()).filter(i => !!i);
|
|
111
|
+
const result: IOidcTokenInfo[] = [];
|
|
112
|
+
types.forEach(item => {
|
|
113
|
+
const info = Tokens.createTokenInfo(item, params, time, scopes);
|
|
114
|
+
if (info) {
|
|
115
|
+
result.push(info);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Finishes the authorization.
|
|
123
|
+
*/
|
|
124
|
+
finish(tokens: (IOidcTokenInfo|IOidcTokenError)[]): void {
|
|
125
|
+
if (this[resolveFunction]) {
|
|
126
|
+
this[resolveFunction]!(tokens as any);
|
|
127
|
+
}
|
|
128
|
+
this[rejectFunction] = undefined;
|
|
129
|
+
this[resolveFunction] = undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Processes token info object when it's ready.
|
|
134
|
+
*
|
|
135
|
+
* @param info Token info returned from the server.
|
|
136
|
+
*/
|
|
137
|
+
[handleTokenInfo](info: ITokenInfo): void {
|
|
138
|
+
const { responseType } = this.settings;
|
|
139
|
+
const token = Tokens.fromTokenInfo(info);
|
|
140
|
+
token.responseType = responseType || '';
|
|
141
|
+
this.finish([token]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { IOAuth2Authorization } from "../models/Authorization.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if the URL has valid scheme for OAuth flow.
|
|
5
|
+
*
|
|
6
|
+
* Do not use this to validate redirect URIs as they can use any protocol.
|
|
7
|
+
*
|
|
8
|
+
* @param url The url value to test
|
|
9
|
+
* @throws {TypeError} When passed value is not set, empty, or not a string
|
|
10
|
+
* @throws {Error} When passed value is not a valid URL for OAuth 2 flow
|
|
11
|
+
*/
|
|
12
|
+
export function checkUrl(url: string): void {
|
|
13
|
+
if (!url) {
|
|
14
|
+
throw new TypeError("the value is missing");
|
|
15
|
+
}
|
|
16
|
+
if (typeof url !== "string") {
|
|
17
|
+
throw new TypeError("the value is not a string");
|
|
18
|
+
}
|
|
19
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
20
|
+
throw new Error("the value has invalid scheme");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks if basic configuration of the OAuth 2 request is valid an can proceed
|
|
26
|
+
* with authentication.
|
|
27
|
+
* @param settings authorization settings
|
|
28
|
+
* @throws {Error} When settings are not valid
|
|
29
|
+
*/
|
|
30
|
+
export function sanityCheck(settings: IOAuth2Authorization): void {
|
|
31
|
+
if (["implicit", "authorization_code"].includes(settings.grantType!)) {
|
|
32
|
+
try {
|
|
33
|
+
checkUrl(settings.authorizationUri!);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
throw new Error(`authorizationUri: ${(e as Error).message}`);
|
|
36
|
+
}
|
|
37
|
+
if (settings.accessTokenUri) {
|
|
38
|
+
try {
|
|
39
|
+
checkUrl(settings.accessTokenUri);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new Error(`accessTokenUri: ${(e as Error).message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else if (settings.accessTokenUri) {
|
|
45
|
+
try {
|
|
46
|
+
checkUrl(settings.accessTokenUri);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
throw new Error(`accessTokenUri: ${(e as Error).message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generates a random string of characters.
|
|
55
|
+
*
|
|
56
|
+
* @returns A random string.
|
|
57
|
+
*/
|
|
58
|
+
export function randomString(): string {
|
|
59
|
+
const array = new Uint32Array(28);
|
|
60
|
+
window.crypto.getRandomValues(array);
|
|
61
|
+
return Array.from(array, (dec) => `0${dec.toString(16)}`.substr(-2)).join("");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Replaces `-` or `_` with camel case.
|
|
66
|
+
* @param {string} name The string to process
|
|
67
|
+
* @return {String|undefined} Camel cased string or `undefined` if not transformed.
|
|
68
|
+
*/
|
|
69
|
+
export function camel(name: string): string | undefined {
|
|
70
|
+
let i = 0;
|
|
71
|
+
let l;
|
|
72
|
+
let changed = false;
|
|
73
|
+
// eslint-disable-next-line no-cond-assign
|
|
74
|
+
while ((l = name[i])) {
|
|
75
|
+
if ((l === "_" || l === "-") && i + 1 < name.length) {
|
|
76
|
+
// eslint-disable-next-line no-param-reassign
|
|
77
|
+
name = name.substr(0, i) + name[i + 1].toUpperCase() + name.substr(i + 2);
|
|
78
|
+
changed = true;
|
|
79
|
+
}
|
|
80
|
+
// eslint-disable-next-line no-plusplus
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
return changed ? name : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Computes the SHA256 hash ogf the given input.
|
|
88
|
+
* @param value The value to encode.
|
|
89
|
+
*/
|
|
90
|
+
export async function sha256(value: string): Promise<ArrayBuffer> {
|
|
91
|
+
const encoder = new TextEncoder();
|
|
92
|
+
const data = encoder.encode(value);
|
|
93
|
+
return window.crypto.subtle.digest("SHA-256", data);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Encoded the array buffer to a base64 string value.
|
|
98
|
+
*/
|
|
99
|
+
export function base64Buffer(buffer: ArrayBuffer): string {
|
|
100
|
+
const view = new Uint8Array(buffer);
|
|
101
|
+
const str = String.fromCharCode.apply(null, view as any);
|
|
102
|
+
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Generates code challenge for the PKCE extension to the OAuth2 specification.
|
|
107
|
+
* @param verifier The generated code verifier.
|
|
108
|
+
* @returns The code challenge string
|
|
109
|
+
*/
|
|
110
|
+
export async function generateCodeChallenge(verifier: string): Promise<string> {
|
|
111
|
+
const hashed = await sha256(verifier);
|
|
112
|
+
return base64Buffer(hashed);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generates cryptographically significant random string.
|
|
117
|
+
* @param size The size of the generated nonce.
|
|
118
|
+
* @returns A nonce (number used once).
|
|
119
|
+
*/
|
|
120
|
+
export function nonceGenerator(size = 20): string {
|
|
121
|
+
const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
122
|
+
let array = new Uint8Array(size);
|
|
123
|
+
window.crypto.getRandomValues(array);
|
|
124
|
+
array = array.map(x => validChars.charCodeAt(x % validChars.length));
|
|
125
|
+
return String.fromCharCode.apply(null, array as any);
|
|
126
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
export const loadHandler = Symbol('loadHandler');
|
|
3
|
+
export const timeoutValue = Symbol('timeoutValue');
|
|
4
|
+
export const checkInterval = Symbol('checkInterval');
|
|
5
|
+
export const limitValue = Symbol('limitValue');
|
|
6
|
+
export const targetValue = Symbol('targetValue');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Adds the support for the iframe authorization.
|
|
10
|
+
*
|
|
11
|
+
* This class creates and loads an iframe for the given URL. Then it runs a timer recursively
|
|
12
|
+
* that increases its interval multiplying its last timeout value by 2.
|
|
13
|
+
* The the timer reaches timeout it dispatches the timeout event.
|
|
14
|
+
*
|
|
15
|
+
* The library takes into the account the redirects.
|
|
16
|
+
*
|
|
17
|
+
* Call the `cancel()` and the `cleanUp()` functions when the authorization data is received.
|
|
18
|
+
*/
|
|
19
|
+
export class IframeAuthorization extends EventTarget {
|
|
20
|
+
loadTimeout?: any;
|
|
21
|
+
|
|
22
|
+
timedOut = false;
|
|
23
|
+
|
|
24
|
+
[timeoutValue] = 8;
|
|
25
|
+
|
|
26
|
+
[limitValue]: number;
|
|
27
|
+
|
|
28
|
+
[targetValue]: HTMLElement;
|
|
29
|
+
|
|
30
|
+
frame?: HTMLIFrameElement;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param limit The timeout limit after which the library calls timeout if it wasn't cancelled.
|
|
34
|
+
* @param target A target node where to add the iframe into.
|
|
35
|
+
*/
|
|
36
|
+
constructor(limit=1020, target:HTMLElement=document.body) {
|
|
37
|
+
super();
|
|
38
|
+
this[limitValue] = limit;
|
|
39
|
+
this[targetValue] = target;
|
|
40
|
+
|
|
41
|
+
this[loadHandler] = this[loadHandler].bind(this);
|
|
42
|
+
this[checkInterval] = this[checkInterval].bind(this);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A function to be called when the timer is no longer needed.
|
|
47
|
+
*/
|
|
48
|
+
cancel(): void {
|
|
49
|
+
if (this.loadTimeout) {
|
|
50
|
+
clearTimeout(this.loadTimeout);
|
|
51
|
+
this.loadTimeout = undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Removes any existing frame and removes any remaining listeners.
|
|
57
|
+
*/
|
|
58
|
+
cleanUp(): void {
|
|
59
|
+
if (!this.frame) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.frame.removeEventListener('load', this[loadHandler]);
|
|
63
|
+
if (this.frame.parentElement) {
|
|
64
|
+
this.frame.parentElement.removeChild(this.frame);
|
|
65
|
+
}
|
|
66
|
+
this.frame = undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates an invisible frame and loads the given URL
|
|
71
|
+
* @param url THe resource to load.
|
|
72
|
+
*/
|
|
73
|
+
load(url: string): void {
|
|
74
|
+
const iframe = document.createElement('iframe');
|
|
75
|
+
iframe.style.border = '0';
|
|
76
|
+
iframe.style.width = '0';
|
|
77
|
+
iframe.style.height = '0';
|
|
78
|
+
iframe.style.overflow = 'hidden';
|
|
79
|
+
iframe.addEventListener('load', this[loadHandler]);
|
|
80
|
+
iframe.id = 'oauth2-authorization-frame';
|
|
81
|
+
iframe.setAttribute('data-owner', 'oauth2-authorization');
|
|
82
|
+
iframe.setAttribute('aria-hidden', 'true');
|
|
83
|
+
this[targetValue].appendChild(iframe);
|
|
84
|
+
iframe.src = url;
|
|
85
|
+
this.frame = iframe;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handler for the `load` event on the frame.
|
|
90
|
+
* @param {Event} e
|
|
91
|
+
*/
|
|
92
|
+
[loadHandler](e: Event): void {
|
|
93
|
+
this.cancel();
|
|
94
|
+
const iframe = e.target as HTMLIFrameElement
|
|
95
|
+
try {
|
|
96
|
+
if (iframe.contentWindow) {
|
|
97
|
+
iframe.contentWindow.addEventListener('unload', () => {
|
|
98
|
+
this.cancel();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
} catch (_) {
|
|
102
|
+
// ...
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this[timeoutValue] = 8;
|
|
106
|
+
this.loadTimeout = setTimeout(this[checkInterval], this[timeoutValue]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A callback function that runs in the timeout.
|
|
111
|
+
* It calls the timeout if the timeout value reaches the limit or increases the timeout value
|
|
112
|
+
* and runs the counter once again.
|
|
113
|
+
*/
|
|
114
|
+
[checkInterval](): void {
|
|
115
|
+
if (this.timedOut) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (this[timeoutValue] >= this[limitValue]) {
|
|
119
|
+
this.cancel();
|
|
120
|
+
this.dispatchEvent(new Event('timeout'));
|
|
121
|
+
this.timedOut = true;
|
|
122
|
+
this.cleanUp();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this[timeoutValue] *= 2;
|
|
126
|
+
this.loadTimeout = setTimeout(this[checkInterval], this[timeoutValue]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const implicit = 'implicit';
|
|
2
|
+
export const code = 'authorization_code';
|
|
3
|
+
export const clientCredentials = 'client_credentials';
|
|
4
|
+
export const password = 'password';
|
|
5
|
+
export const jwtBearer = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
|
|
6
|
+
export const deviceCode = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export const observePopupState = Symbol('observePopupState');
|
|
2
|
+
export const popupInterval = Symbol('popupInterval');
|
|
3
|
+
export const popupObserver = Symbol('popupObserver');
|
|
4
|
+
export const intervalValue = Symbol('intervalValue');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Adds support for the popup window authorization.
|
|
8
|
+
*
|
|
9
|
+
* The set timeout hack is used because I can't see other way of doing this
|
|
10
|
+
* as load/unload events are called only once (even with redirects)
|
|
11
|
+
* and there's no way of knowing what is happening in the popup (so no timeouts).
|
|
12
|
+
* The user may need more time to authorize themselves and then the application.
|
|
13
|
+
*
|
|
14
|
+
* This class dispatches the `close` event when the popup was closed.
|
|
15
|
+
*
|
|
16
|
+
* Call the `cleanUp()` function when the authorization data is received.
|
|
17
|
+
*/
|
|
18
|
+
export class PopupAuthorization extends EventTarget {
|
|
19
|
+
[intervalValue]: number;
|
|
20
|
+
|
|
21
|
+
popup?: Window;
|
|
22
|
+
|
|
23
|
+
[popupInterval]: any;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param interval The popup state check interval
|
|
27
|
+
*/
|
|
28
|
+
constructor(interval = 50) {
|
|
29
|
+
super();
|
|
30
|
+
this[intervalValue] = interval;
|
|
31
|
+
this[popupObserver] = this[popupObserver].bind(this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Removes any existing frame and removes any remaining listeners.
|
|
36
|
+
*/
|
|
37
|
+
cleanUp(): void {
|
|
38
|
+
if (this[popupInterval]) {
|
|
39
|
+
clearInterval(this[popupInterval]);
|
|
40
|
+
this[popupInterval] = undefined;
|
|
41
|
+
}
|
|
42
|
+
const { popup } = this;
|
|
43
|
+
if (popup && !popup.closed) {
|
|
44
|
+
popup.close();
|
|
45
|
+
}
|
|
46
|
+
this.popup = undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Opens a popup to request authorization from the user.
|
|
51
|
+
* @param url The URL to open.
|
|
52
|
+
*/
|
|
53
|
+
load(url: string): void {
|
|
54
|
+
const op = 'menubar=no,location=no,resizable=yes,scrollbars=yes,status=no,width=800,height=600';
|
|
55
|
+
const popup = window.open(url, 'oauth-window', op);
|
|
56
|
+
if (!popup) {
|
|
57
|
+
throw new Error('Authorization popup is being blocked.');
|
|
58
|
+
}
|
|
59
|
+
popup.window.focus();
|
|
60
|
+
this.popup = popup;
|
|
61
|
+
this[observePopupState]();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Initializes an interval to check whether the popup window is still present.
|
|
66
|
+
* The web security model does not allow pages to read the URL for the cross domain
|
|
67
|
+
* connections.
|
|
68
|
+
*/
|
|
69
|
+
[observePopupState](): void {
|
|
70
|
+
this[popupInterval] = setInterval(this[popupObserver], this[intervalValue]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[popupObserver](): void {
|
|
74
|
+
const { popup } = this;
|
|
75
|
+
if (!popup || popup.closed) {
|
|
76
|
+
this.cleanUp();
|
|
77
|
+
this.dispatchEvent(new Event('close'));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { IOidcTokenInfo, ITokenInfo } from "../../models/Authorization.js";
|
|
2
|
+
|
|
3
|
+
export class Tokens {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a OidcTokenInfo object for the corresponding response type.
|
|
6
|
+
*
|
|
7
|
+
* @param responseType The response type of the token to prepare the info for.
|
|
8
|
+
* @param params params received from the authorization endpoint.
|
|
9
|
+
* @param time Timestamp when the tokens were created
|
|
10
|
+
* @param requestedScopes The list of requested scopes. Optional.
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
static createTokenInfo(responseType: string, params: URLSearchParams, time: number, requestedScopes?: string[]): IOidcTokenInfo | null {
|
|
14
|
+
switch (responseType) {
|
|
15
|
+
case 'code': return Tokens.createCodeToken(params, time, requestedScopes);
|
|
16
|
+
case 'token': return Tokens.createTokenToken(params, time, requestedScopes);
|
|
17
|
+
case 'id_token': return Tokens.createIdTokenToken(params, time, requestedScopes);
|
|
18
|
+
default: return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a "code" response type token info.
|
|
24
|
+
* @param params
|
|
25
|
+
* @param time Timestamp when the tokens were created
|
|
26
|
+
* @param requestedScopes The list of requested scopes. Optional.
|
|
27
|
+
* @returns
|
|
28
|
+
*/
|
|
29
|
+
static createBaseToken(params: URLSearchParams, time: number, requestedScopes?: string[]): IOidcTokenInfo {
|
|
30
|
+
const scope = Tokens.computeTokenInfoScopes(requestedScopes, params.get('scope')!);
|
|
31
|
+
const tokenInfo: IOidcTokenInfo = {
|
|
32
|
+
state: params.get('state')!,
|
|
33
|
+
expiresIn: Number(params.get('expires_in')),
|
|
34
|
+
tokenType: params.get('token_type')!,
|
|
35
|
+
scope,
|
|
36
|
+
time,
|
|
37
|
+
responseType: '',
|
|
38
|
+
};
|
|
39
|
+
return tokenInfo;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a "code" response type token info.
|
|
44
|
+
* @param params
|
|
45
|
+
* @param time Timestamp when the tokens were created
|
|
46
|
+
* @param requestedScopes The list of requested scopes. Optional.
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
static createCodeToken(params: URLSearchParams, time: number, requestedScopes?: string[]): IOidcTokenInfo {
|
|
50
|
+
const token = Tokens.createBaseToken(params, time, requestedScopes);
|
|
51
|
+
token.responseType = 'code';
|
|
52
|
+
token.code = params.get('code')!;
|
|
53
|
+
return token;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a "token" response type token info.
|
|
58
|
+
* @param params
|
|
59
|
+
* @param time Timestamp when the tokens were created
|
|
60
|
+
* @param requestedScopes The list of requested scopes. Optional.
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
63
|
+
static createTokenToken(params: URLSearchParams, time: number, requestedScopes?: string[]): IOidcTokenInfo {
|
|
64
|
+
const token = Tokens.createBaseToken(params, time, requestedScopes);
|
|
65
|
+
token.responseType = 'token';
|
|
66
|
+
token.accessToken = params.get('access_token')!;
|
|
67
|
+
token.refreshToken = params.get('refresh_token')!;
|
|
68
|
+
return token;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates a "id_token" response type token info.
|
|
73
|
+
* @param time Timestamp when the tokens were created
|
|
74
|
+
* @param requestedScopes The list of requested scopes. Optional.
|
|
75
|
+
*/
|
|
76
|
+
static createIdTokenToken(params: URLSearchParams, time: number, requestedScopes?: string[]): IOidcTokenInfo {
|
|
77
|
+
const token = Tokens.createBaseToken(params, time, requestedScopes);
|
|
78
|
+
token.responseType = 'id_token';
|
|
79
|
+
token.accessToken = params.get('access_token')!;
|
|
80
|
+
token.refreshToken = params.get('refresh_token')!;
|
|
81
|
+
token.idToken = params.get('id_token')!;
|
|
82
|
+
return token;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Computes the final list of granted scopes.
|
|
87
|
+
* It is a list of scopes received in the response or the list of requested scopes.
|
|
88
|
+
* Because the user may change the list of scopes during the authorization process
|
|
89
|
+
* the received list of scopes can be different than the one requested by the user.
|
|
90
|
+
*
|
|
91
|
+
* @param requestedScopes The list of requested scopes. Optional.
|
|
92
|
+
* @param tokenScopes The `scope` parameter received with the response. It's null safe.
|
|
93
|
+
* @returns The list of scopes for the token.
|
|
94
|
+
*/
|
|
95
|
+
static computeTokenInfoScopes(requestedScopes?: string[], tokenScopes?: string): string[] {
|
|
96
|
+
if (!tokenScopes && requestedScopes) {
|
|
97
|
+
return requestedScopes;
|
|
98
|
+
}
|
|
99
|
+
let listScopes: string[] = [];
|
|
100
|
+
if (typeof tokenScopes === 'string') {
|
|
101
|
+
listScopes = tokenScopes.split(' ');
|
|
102
|
+
}
|
|
103
|
+
return listScopes;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static fromTokenInfo(info: ITokenInfo): IOidcTokenInfo {
|
|
107
|
+
const result: IOidcTokenInfo = {
|
|
108
|
+
responseType: '',
|
|
109
|
+
state: info.state,
|
|
110
|
+
accessToken: info.accessToken,
|
|
111
|
+
time: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
if (info.scope) {
|
|
114
|
+
result.scope = info.scope;
|
|
115
|
+
}
|
|
116
|
+
if (info.tokenType) {
|
|
117
|
+
result.tokenType = info.tokenType;
|
|
118
|
+
}
|
|
119
|
+
if (info.expiresIn) {
|
|
120
|
+
result.expiresIn = info.expiresIn;
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
}
|