Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): persist cookie based apiKey in document.cookie #8689

Merged
merged 2 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
})
})
})
})
})