diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts index 84a855ed66833348dd6ef805f4c8b0c965117f1b..99b758e2944cba705f1f192735f6936da69f00c7 100644 --- a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts +++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.test.ts @@ -15,6 +15,7 @@ export default class KeycloakOAuth2DeviceFlowConnectionTest { this.connection = new KeycloakOAuth2DeviceFlowConnection( 'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device', 'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token', + 'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo', ); } @@ -38,5 +39,20 @@ export default class KeycloakOAuth2DeviceFlowConnectionTest { openLink: openLinkPlaceholder, }); console.log('refreshed tokens', refreshedTokens); + + try { + console.log(`'12345' is a valid token ? ${await this.connection.tokenIsValid('12345')};`); + console.log(`'' is a valid token ? ${await this.connection.tokenIsValid('')};`); + console.log( + `${tokens.accessToken} is a valid token ? ${await this.connection.tokenIsValid(tokens.accessToken)};`, + ); + console.log( + `${refreshedTokens.accessToken} is a valid token ? ${await this.connection.tokenIsValid( + refreshedTokens.accessToken, + )};`, + ); + } catch (error: unknown) { + console.error(error); + } } } diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts index 06be288f3b5be1f856e10e04dd7e9cd16432622e..7a34587675991c65d4d3966d816b3a525f3de00f 100644 --- a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts +++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts @@ -12,13 +12,54 @@ export default class KeycloakOAuth2DeviceFlowConnection { private _refreshToken: string; private _deviceAuthorizationRequestResponseData: DeviceAuthorizationRequestResponseData; - constructor(private _deviceUrl: string, private _tokenUrl: string) { + constructor(private _deviceUrl: string, private _tokenUrl: string, private _userInfoUrl?: string) { this._waitDuration = new WaitDuration([5_000, 5_000, 5_000, 10_000, 10_000, 10_000, 30_000, 30_000, 100_000]); this._accessToken = ''; this._refreshToken = ''; this._deviceAuthorizationRequestResponseData = {}; } + /** + * _userInfoUrl must be passed in constructor in order to use this + * @param accessToken + * @returns Promise + */ + public async tokenIsValid(accessToken: string) { + const url = this._userInfoUrl; + if (!url) { + return Promise.reject('tokenIsValid: missing user_info API endpoint'); + } + if (!accessToken) { + return Promise.resolve(false); + } + const tokenValidationRequestResponse: Response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Bearer ${accessToken}`, + }, + body: '', + agent: new https.Agent({ rejectUnauthorized: false }), // TODO: remove when SSL will work + }); + const tokenValidationRequestResponseCode = tokenValidationRequestResponse.status; + switch (tokenValidationRequestResponseCode) { + case HttpStatusCode.OK: { + return Promise.resolve(true); + } + case HttpStatusCode.BAD_REQUEST: { + const badRequestResponse: FailedAuthenticationReponseData = + (await tokenValidationRequestResponse.json()) as FailedAuthenticationReponseData; + throw new Error('tokenIsValid: ' + badRequestResponse.error); + } + case HttpStatusCode.UNAUTHORIZED: { + return Promise.resolve(false); + } + default: { + throw new Error(`tokenIsValid: Unhandled HTTP status: ${tokenValidationRequestResponseCode}`); + } + } + } + public async getToken(args: { refreshToken?: string; openLink: (link: string) => void }) { const { refreshToken, openLink } = args; if (!!refreshToken) { @@ -145,7 +186,7 @@ export default class KeycloakOAuth2DeviceFlowConnection { break; } default: { - break; + throw new Error(`tokenIsValid: Unhandled HTTP status: ${userAuthenticationRequestResponseCode}`); } } }