Skip to content

Commit

Permalink
feat(auth): persist cookie based apiKey in document.cookie (#8689)
Browse files Browse the repository at this point in the history
Refs #8683
  • Loading branch information
char0n authored May 22, 2023
1 parent 26709c3 commit 7ac9a8f
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 64 deletions.
11 changes: 6 additions & 5 deletions src/core/plugins/auth/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,12 @@ export function restoreAuthorization(payload) {

export const persistAuthorizationIfNeeded = () => ( { authSelectors, getConfigs } ) => {
const configs = getConfigs()
if (configs.persistAuthorization)
{
const authorized = authSelectors.authorized()
localStorage.setItem("authorized", JSON.stringify(authorized.toJS()))
}

if (!configs.persistAuthorization) return

// persist authorization to local storage
const authorized = authSelectors.authorized().toJS()
localStorage.setItem("authorized", JSON.stringify(authorized))
}

export const authPopup = (url, swaggerUIRedirectOauth2) => ( ) => {
Expand Down
19 changes: 19 additions & 0 deletions src/core/plugins/auth/configs-extensions/wrap-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @prettier
*/
export const loaded = (oriAction, system) => (payload) => {
const { getConfigs, authActions } = system
const configs = getConfigs()

oriAction(payload)

// check if we should restore authorization data from localStorage
if (configs.persistAuthorization) {
const authorized = localStorage.getItem("authorized")
if (authorized) {
authActions.restoreAuthorization({
authorized: JSON.parse(authorized),
})
}
}
}
21 changes: 17 additions & 4 deletions src/core/plugins/auth/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import reducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
import * as specWrapActionReplacements from "./spec-wrap-actions"
import { execute as wrappedExecuteAction } from "./spec-extensions/wrap-actions"
import { loaded as wrappedLoadedAction } from "./configs-extensions/wrap-actions"
import { authorize as wrappedAuthorizeAction, logout as wrappedLogoutAction } from "./wrap-actions"

export default function() {
return {
Expand All @@ -15,11 +17,22 @@ export default function() {
auth: {
reducers,
actions,
selectors
selectors,
wrapActions: {
authorize: wrappedAuthorizeAction,
logout: wrappedLogoutAction,
}
},
configs: {
wrapActions: {
loaded: wrappedLoadedAction,
},
},
spec: {
wrapActions: specWrapActionReplacements
}
wrapActions: {
execute: wrappedExecuteAction,
},
},
}
}
}
Expand Down
File renamed without changes.
64 changes: 64 additions & 0 deletions src/core/plugins/auth/wrap-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @prettier
*/

/**
* `authorize` and `logout` wrapped actions provide capacity
* to persist cookie based apiKey in document.cookie.
*
* `persistAuthorization` SwaggerUI options needs to set to `true`
* for document.cookie persistence to work.
*/
export const authorize = (oriAction, system) => (payload) => {
oriAction(payload)

const configs = system.getConfigs()

if (!configs.persistAuthorization) return

// create cookie
try {
const [{ schema, value }] = Object.values(payload)
const isApiKeyAuth = schema.get("type") === "apiKey"
const isInCookie = schema.get("in") === "cookie"
const isApiKeyInCookie = isApiKeyAuth && isInCookie

if (isApiKeyInCookie) {
document.cookie = `${schema.get("name")}=${value}; SameSite=None; Secure`
}
} catch (error) {
console.error(
"Error persisting cookie based apiKey in document.cookie.",
error
)
}
}

export const logout = (oriAction, system) => (payload) => {
const configs = system.getConfigs()
const authorized = system.authSelectors.authorized()

// deleting cookie
try {
if (configs.persistAuthorization && Array.isArray(payload)) {
payload.forEach((authorizedName) => {
const auth = authorized.get(authorizedName, {})
const isApiKeyAuth = auth.getIn(["schema", "type"]) === "apiKey"
const isInCookie = auth.getIn(["schema", "in"]) === "cookie"
const isApiKeyInCookie = isApiKeyAuth && isInCookie

if (isApiKeyInCookie) {
const cookieName = auth.getIn(["schema", "name"])
document.cookie = `${cookieName}=; Max-Age=-99999999`
}
})
}
} catch (error) {
console.error(
"Error deleting cookie based apiKey from document.cookie.",
error
)
}

oriAction(payload)
}
15 changes: 2 additions & 13 deletions src/core/plugins/configs/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,6 @@ export function toggle(configName) {


// Hook
export const loaded = () => ({getConfigs, authActions}) => {
// check if we should restore authorization data from localStorage
const configs = getConfigs()
if (configs.persistAuthorization)
{
const authorized = localStorage.getItem("authorized")
if(authorized)
{
authActions.restoreAuthorization({
authorized: JSON.parse(authorized)
})
}
}
export const loaded = () => () => {
// noop
}
41 changes: 41 additions & 0 deletions test/unit/core/plugins/auth/configs-extensions/wrap-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { loaded } from "corePlugins/auth/configs-extensions/wrap-actions"

describe("loaded hook", () => {
describe("authorization data restoration", () => {
beforeEach(() => {
localStorage.clear()
})
it("retrieve `authorized` value from `localStorage`", () => {
const system = {
getConfigs: () => ({
persistAuthorization: true
}),
authActions: {

}
}
jest.spyOn(Object.getPrototypeOf(window.localStorage), "getItem")

loaded(jest.fn(), system)()
expect(localStorage.getItem).toHaveBeenCalled()
expect(localStorage.getItem).toHaveBeenCalledWith("authorized")
})
it("restore authorization data when a value exists", () => {
const system = {
getConfigs: () => ({
persistAuthorization: true
}),
authActions: {
restoreAuthorization: jest.fn(() => {})
}
}
const mockData = {"api_key": {}}
localStorage.setItem("authorized", JSON.stringify(mockData))
loaded(jest.fn(), system)()
expect(system.authActions.restoreAuthorization).toHaveBeenCalled()
expect(system.authActions.restoreAuthorization).toHaveBeenCalledWith({
authorized: mockData
})
})
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

import { execute } from "corePlugins/auth/spec-wrap-actions"
import { execute } from "corePlugins/auth/spec-extensions/wrap-actions"

describe("spec plugin - actions", function(){

Expand Down
119 changes: 119 additions & 0 deletions test/unit/core/plugins/auth/wrap-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @prettier
*/
import { fromJS } from "immutable"
import { authorize, logout } from "corePlugins/auth/wrap-actions"

describe("Cookie based apiKey persistence in document.cookie", () => {
beforeEach(() => {
let cookieJar = ""
jest.spyOn(document, "cookie", "set").mockImplementation((cookie) => {
cookieJar += cookie
})
jest.spyOn(document, "cookie", "get").mockImplementation(() => cookieJar)
})

afterEach(() => {
jest.restoreAllMocks()
})

describe("given persistAuthorization=true", () => {
it("should persist cookie in document.cookie", () => {
const system = {
getConfigs: () => ({
persistAuthorization: true,
}),
}
const payload = {
api_key: {
schema: fromJS({
type: "apiKey",
name: "apiKeyCookie",
in: "cookie",
}),
value: "test",
},
}

authorize(jest.fn(), system)(payload)

expect(document.cookie).toEqual(
"apiKeyCookie=test; SameSite=None; Secure"
)
})

it("should delete cookie from document.cookie", () => {
const payload = fromJS({
api_key: {
schema: {
type: "apiKey",
name: "apiKeyCookie",
in: "cookie",
},
value: "test",
},
})
const system = {
getConfigs: () => ({
persistAuthorization: true,
}),
authSelectors: {
authorized: () => payload,
},
}

logout(jest.fn(), system)(["api_key"])

expect(document.cookie).toEqual("apiKeyCookie=; Max-Age=-99999999")
})
})

describe("given persistAuthorization=false", () => {
it("shouldn't persist cookie in document.cookie", () => {
const system = {
getConfigs: () => ({
persistAuthorization: false,
}),
}
const payload = {
api_key: {
schema: fromJS({
type: "apiKey",
name: "apiKeyCookie",
in: "cookie",
}),
value: "test",
},
}

authorize(jest.fn(), system)(payload)

expect(document.cookie).toEqual("")
})

it("should delete cookie from document.cookie", () => {
const payload = fromJS({
api_key: {
schema: {
type: "apiKey",
name: "apiKeyCookie",
in: "cookie",
},
value: "test",
},
})
const system = {
getConfigs: () => ({
persistAuthorization: false,
}),
authSelectors: {
authorized: () => payload,
},
}

logout(jest.fn(), system)(["api_key"])

expect(document.cookie).toEqual("")
})
})
})
40 changes: 0 additions & 40 deletions test/unit/core/plugins/configs/actions.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { downloadConfig } from "corePlugins/configs/spec-actions"
import { loaded } from "corePlugins/configs/actions"

describe("configs plugin - actions", () => {

Expand All @@ -23,43 +22,4 @@ describe("configs plugin - actions", () => {
expect(fetchSpy).toHaveBeenCalledWith(req)
})
})

describe("loaded hook", () => {
describe("authorization data restoration", () => {
beforeEach(() => {
localStorage.clear()
})
it("retrieve `authorized` value from `localStorage`", () => {
const system = {
getConfigs: () => ({
persistAuthorization: true
}),
authActions: {

}
}
jest.spyOn(Object.getPrototypeOf(window.localStorage), "getItem")
loaded()(system)
expect(localStorage.getItem).toHaveBeenCalled()
expect(localStorage.getItem).toHaveBeenCalledWith("authorized")
})
it("restore authorization data when a value exists", () => {
const system = {
getConfigs: () => ({
persistAuthorization: true
}),
authActions: {
restoreAuthorization: jest.fn(() => {})
}
}
const mockData = {"api_key": {}}
localStorage.setItem("authorized", JSON.stringify(mockData))
loaded()(system)
expect(system.authActions.restoreAuthorization).toHaveBeenCalled()
expect(system.authActions.restoreAuthorization).toHaveBeenCalledWith({
authorized: mockData
})
})
})
})
})

0 comments on commit 7ac9a8f

Please sign in to comment.