From 4f7eed7b257129195662dc4567d6b9608778bf9b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 26 Jul 2023 11:16:35 +0200 Subject: [PATCH] auth-node: added duplicate and refactored oauth state codec Signed-off-by: Patrik Oldsberg --- plugins/auth-node/package.json | 1 + plugins/auth-node/src/oauth/state.ts | 67 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 plugins/auth-node/src/oauth/state.ts diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index a570f24bff..800ea22b61 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -38,6 +38,7 @@ "@types/express": "*", "express": "^4.17.1", "jose": "^4.6.0", + "lodash": "^4.17.21", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, diff --git a/plugins/auth-node/src/oauth/state.ts b/plugins/auth-node/src/oauth/state.ts new file mode 100644 index 0000000000..668fe3736a --- /dev/null +++ b/plugins/auth-node/src/oauth/state.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import pickBy from 'lodash/pickBy'; +import { Request } from 'express'; + +export type OAuthState = { + /* A type for the serialized value in the `state` parameter of the OAuth authorization flow + */ + nonce: string; + env: string; + origin?: string; + scope?: string; + redirectUrl?: string; + flow?: string; +}; + +/** @public */ +export type OAuthStateEncoder = ( + state: OAuthState, + context: { req: Request }, +) => Promise<{ encodedState: string }>; + +/** @public */ +export type OAuthStateDecoder = ( + encodedState: string, + context: { req: Request }, +) => Promise<{ state: OAuthState }>; + +/** @public */ +export const defaultStateEncoder: OAuthStateEncoder = async state => { + const stateString = new URLSearchParams( + pickBy(state, value => value !== undefined), + ).toString(); + + return { encodedState: Buffer.from(stateString, 'utf-8').toString('hex') }; +}; + +/** @public */ +export const defaultStateDecoder: OAuthStateDecoder = async encodedState => { + const state = Object.fromEntries( + new URLSearchParams(Buffer.from(encodedState, 'hex').toString('utf-8')), + ); + if ( + !state.nonce || + !state.env || + state.nonce?.length === 0 || + state.env?.length === 0 + ) { + throw Error(`Invalid state passed via request`); + } + + return { state: state as OAuthState }; +};