-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: openIdVariableReplace splitted to separete files and better…
… userSession management
- Loading branch information
Showing
16 changed files
with
527 additions
and
368 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
|
||
export interface UserSession{ | ||
id: string; | ||
title: string; | ||
description: string; | ||
logout?: () => void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { OpenIdConfiguration, assertConfiguration } from './openIdConfiguration'; | ||
import { OpenIdInformation, requestOpenIdInformation } from './openIdInformation'; | ||
import { OpenIdFlow } from './openIdFlow'; | ||
import { toQueryParams } from '../../../utils'; | ||
import { HttpClient, Progress } from '../../../models'; | ||
import open from 'open'; | ||
import { registerListener, unregisterListener } from './openIdHttpserver'; | ||
|
||
class AuthorizationCodeFlow implements OpenIdFlow { | ||
supportsFlow(flow: string): boolean{ | ||
return ['authorization_code', 'code'].indexOf(flow) >= 0; | ||
} | ||
|
||
getCacheKey(config: OpenIdConfiguration) { | ||
if (assertConfiguration(config, ['tokenEndpoint', 'authorizationEndpoint', 'clientId', 'clientSecret'])) { | ||
return `authorization_code_${config.clientId}_${config.tokenEndpoint}`; | ||
} | ||
return false; | ||
} | ||
|
||
async perform(config: OpenIdConfiguration, context: {httpClient: HttpClient, progress: Progress | undefined, cacheKey: string}): Promise<OpenIdInformation | false> { | ||
return new Promise<OpenIdInformation | false>(async (resolve, reject) => { | ||
const state = this.stateGenerator(); | ||
try { | ||
const redirectUri = `http://localhost:${config.port}/callback`; | ||
const authUrl = `${config.authorizationEndpoint}${config.authorizationEndpoint.indexOf('?') > 0 ? '&' : '?'}${toQueryParams({ | ||
client_id: config.clientId, | ||
scope: config.scope || 'openid', | ||
response_type: 'code', | ||
state, | ||
redirect_uri: redirectUri | ||
})}`; | ||
|
||
let unregisterProgress: (() => void) | undefined; | ||
if (context.progress) { | ||
unregisterProgress = context.progress.register(() => { | ||
unregisterListener(state); | ||
reject(); | ||
}); | ||
} | ||
|
||
registerListener({ | ||
id: state, | ||
name: `authorization for ${config.clientId}: ${config.authorizationEndpoint}`, | ||
resolve: (params) => { | ||
if (params.code && params.state === state) { | ||
if (unregisterProgress) { | ||
unregisterProgress(); | ||
} | ||
const openIdInformation = requestOpenIdInformation({ | ||
url: config.tokenEndpoint, | ||
method: 'POST', | ||
headers: { | ||
'authorization': `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64')}`, | ||
'content-type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: toQueryParams({ | ||
grant_type: 'authorization_code', | ||
scope: config.scope, | ||
code: params.code, | ||
redirect_uri: redirectUri | ||
}) | ||
}, { | ||
httpClient: context.httpClient, | ||
config: config, | ||
id: context.cacheKey, | ||
title: `authorization_code: ${config.clientId}`, | ||
description: config.tokenEndpoint | ||
}); | ||
resolve(openIdInformation); | ||
return { | ||
valid: true, | ||
message: 'code received.', | ||
statusMessage: 'code and state valid. starting code exchange' | ||
}; | ||
} | ||
|
||
return { | ||
valid: false, | ||
message: 'no code received', | ||
statusMessage: 'no code received' | ||
}; | ||
}, | ||
reject, | ||
}); | ||
await open(authUrl); | ||
} catch (err) { | ||
unregisterListener(state); | ||
reject(err); | ||
} | ||
}); | ||
} | ||
|
||
private stateGenerator(length: number = 30) { | ||
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; | ||
const result = []; | ||
for (var i = length; i > 0; --i){ | ||
result.push(chars[Math.floor(Math.random() * chars.length)]); | ||
} | ||
return result.join(''); | ||
} | ||
} | ||
|
||
|
||
export const authorizationCodeFlow = new AuthorizationCodeFlow(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { OpenIdConfiguration, assertConfiguration } from './openIdConfiguration'; | ||
import { OpenIdInformation, requestOpenIdInformation } from './openIdInformation'; | ||
import { OpenIdFlow } from './openIdFlow'; | ||
import { toQueryParams } from '../../../utils'; | ||
import { HttpClient } from '../../../models'; | ||
|
||
class ClientCredentialsFlow implements OpenIdFlow { | ||
supportsFlow(flow: string): boolean{ | ||
return ['client_credentials', 'client'].indexOf(flow) >= 0; | ||
} | ||
|
||
getCacheKey(config: OpenIdConfiguration) { | ||
if (assertConfiguration(config, ['tokenEndpoint', 'clientId', 'clientSecret'])) { | ||
return `client_credentials_${config.clientId}_${config.tokenEndpoint}`; | ||
} | ||
return false; | ||
} | ||
|
||
|
||
async perform(config: OpenIdConfiguration, context: {httpClient: HttpClient, cacheKey: string}): Promise<OpenIdInformation | false> { | ||
return requestOpenIdInformation({ | ||
url: config.tokenEndpoint, | ||
method: 'POST', | ||
headers: { | ||
'authorization': `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64')}`, | ||
'content-type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: toQueryParams({ | ||
grant_type: 'client_credentials', | ||
scope: config.scope | ||
}) | ||
}, { | ||
httpClient: context.httpClient, | ||
config: config, | ||
id: context.cacheKey, | ||
title: `clientCredentials: ${config.clientId}`, | ||
description: config.tokenEndpoint | ||
}); | ||
} | ||
} | ||
|
||
export const clientCredentialsFlow = new ClientCredentialsFlow(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export * from './authorizationCodeFlow'; | ||
export * from './clientCredentialsFlow'; | ||
export * from './openIdConfiguration'; | ||
export * from './openIdFlow'; | ||
export * from './openIdInformation'; | ||
export * from './refreshTokenFlow'; | ||
export * from './passwordFlow'; | ||
export * from './tokenExchangeFlow'; |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import get from 'lodash/get'; | ||
import { popupService, log } from '../../../logger'; | ||
|
||
export interface OpenIdConfiguration{ | ||
variablePrefix: string; | ||
authorizationEndpoint: string; | ||
tokenEndpoint: string; | ||
clientId: string; | ||
clientSecret: string; | ||
scope: string; | ||
keepAlive: boolean; | ||
username?: string; | ||
password?: string; | ||
port?: string; | ||
subjectIssuer?: string; | ||
noLog?: boolean; | ||
} | ||
|
||
export function getOpenIdConfiguration(variablePrefix: string, variables: Record<string, any>) { | ||
if (variablePrefix) { | ||
const getVariable = (name: string) => variables[`${variablePrefix}_${name}`] || get(variables, `${variablePrefix}.${name}`); | ||
|
||
const config: OpenIdConfiguration = { | ||
variablePrefix, | ||
authorizationEndpoint: getVariable('authorizationEndpoint'), | ||
tokenEndpoint: getVariable('tokenEndpoint'), | ||
clientId: getVariable('clientId'), | ||
clientSecret: getVariable('clientSecret'), | ||
scope: getVariable('scope'), | ||
username: getVariable('username'), | ||
password: getVariable('password'), | ||
subjectIssuer: getVariable('subjectIssuer'), | ||
noLog: getVariable('noLog'), | ||
port: getVariable('port') || 3000, | ||
keepAlive: ['false', '0', false].indexOf(getVariable('keepAlive')) < 0, | ||
}; | ||
return config; | ||
} | ||
return false; | ||
} | ||
|
||
|
||
export function assertConfiguration(config: OpenIdConfiguration, keys: string[]) { | ||
const missingKeys = []; | ||
for (const key of keys) { | ||
if (!Object.entries(config).some(([obj, value]) => obj === key && !!value)) { | ||
missingKeys.push(key); | ||
} | ||
} | ||
if (missingKeys.length > 0) { | ||
const message = `missing configuration: ${missingKeys.map(obj => `${config.variablePrefix}_${obj}`).join(', ')}`; | ||
log.error(message); | ||
popupService.error(message); | ||
return false; | ||
} | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { HttpClient, Progress } from '../../../models'; | ||
import { OpenIdConfiguration } from './openIdConfiguration'; | ||
import { OpenIdInformation } from './openIdInformation'; | ||
|
||
|
||
export interface OpenIdFlow{ | ||
supportsFlow(flow: string): boolean; | ||
getCacheKey(config: OpenIdConfiguration): string | false; | ||
perform(config: OpenIdConfiguration, context: {httpClient: HttpClient, progress?: Progress | undefined, cacheKey: string}): Promise<OpenIdInformation | false> | ||
} |
2 changes: 1 addition & 1 deletion
2
src/variables/replacer/openIdHttpserver.ts → ...iables/replacer/oauth/openIdHttpserver.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { OpenIdConfiguration } from './openIdConfiguration'; | ||
import { log, logRequest } from '../../../logger'; | ||
import { HttpClient, HttpClientOptions, HttpResponse, UserSession } from '../../../models'; | ||
import { decodeJWT, isString, toConsoleOutput } from '../../../utils'; | ||
|
||
export interface OpenIdInformation extends UserSession{ | ||
time: number; | ||
config: OpenIdConfiguration; | ||
accessToken: string; | ||
expiresIn: number; | ||
timeSkew: number; | ||
refreshToken?: string; | ||
refreshExpiresIn?: number; | ||
} | ||
|
||
|
||
export async function requestOpenIdInformation(options: HttpClientOptions | false,context: { | ||
config: OpenIdConfiguration, | ||
httpClient: HttpClient, | ||
id: string, | ||
title: string, | ||
description: string, | ||
}): Promise<OpenIdInformation | false>{ | ||
if (options) { | ||
const time = new Date().getTime(); | ||
const response = await context.httpClient(options, { showProgressBar: false }); | ||
if (response) { | ||
response.request = options; | ||
} | ||
return toOpenIdInformation(response, time, context); | ||
} | ||
return false; | ||
} | ||
|
||
export function toOpenIdInformation(response: false | HttpResponse, time: number, context: { | ||
config: OpenIdConfiguration, | ||
id: string, | ||
title: string, | ||
description: string, | ||
}): OpenIdInformation | false { | ||
if (response) { | ||
if (!context.config.noLog) { | ||
logRequest.info(toConsoleOutput(response, true)); | ||
} | ||
if (response.statusCode === 200 && isString(response.body)) { | ||
const jwtToken = JSON.parse(response.body); | ||
const parsedToken = decodeJWT(jwtToken.access_token); | ||
if (!context.config.noLog) { | ||
log.info(JSON.stringify(parsedToken, null, 2)); | ||
} | ||
return { | ||
id: context.id, | ||
title: context.title, | ||
description: context.description, | ||
time, | ||
config: context.config, | ||
accessToken: jwtToken.access_token, | ||
expiresIn: jwtToken.expires_in, | ||
refreshToken: jwtToken.refresh_token, | ||
refreshExpiresIn: jwtToken.refresh_expires_in, | ||
timeSkew: parsedToken?.iat ? Math.floor(time / 1000) - parsedToken.iat : 0, | ||
}; | ||
} | ||
} | ||
return false; | ||
} |
Oops, something went wrong.