Skip to content

Commit 7ac9a8f

Browse files
authored
feat(auth): persist cookie based apiKey in document.cookie (#8689)
Refs #8683
1 parent 26709c3 commit 7ac9a8f

File tree

10 files changed

+269
-64
lines changed

10 files changed

+269
-64
lines changed

src/core/plugins/auth/actions.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,12 @@ export function restoreAuthorization(payload) {
273273

274274
export const persistAuthorizationIfNeeded = () => ( { authSelectors, getConfigs } ) => {
275275
const configs = getConfigs()
276-
if (configs.persistAuthorization)
277-
{
278-
const authorized = authSelectors.authorized()
279-
localStorage.setItem("authorized", JSON.stringify(authorized.toJS()))
280-
}
276+
277+
if (!configs.persistAuthorization) return
278+
279+
// persist authorization to local storage
280+
const authorized = authSelectors.authorized().toJS()
281+
localStorage.setItem("authorized", JSON.stringify(authorized))
281282
}
282283

283284
export const authPopup = (url, swaggerUIRedirectOauth2) => ( ) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @prettier
3+
*/
4+
export const loaded = (oriAction, system) => (payload) => {
5+
const { getConfigs, authActions } = system
6+
const configs = getConfigs()
7+
8+
oriAction(payload)
9+
10+
// check if we should restore authorization data from localStorage
11+
if (configs.persistAuthorization) {
12+
const authorized = localStorage.getItem("authorized")
13+
if (authorized) {
14+
authActions.restoreAuthorization({
15+
authorized: JSON.parse(authorized),
16+
})
17+
}
18+
}
19+
}

src/core/plugins/auth/index.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import reducers from "./reducers"
22
import * as actions from "./actions"
33
import * as selectors from "./selectors"
4-
import * as specWrapActionReplacements from "./spec-wrap-actions"
4+
import { execute as wrappedExecuteAction } from "./spec-extensions/wrap-actions"
5+
import { loaded as wrappedLoadedAction } from "./configs-extensions/wrap-actions"
6+
import { authorize as wrappedAuthorizeAction, logout as wrappedLogoutAction } from "./wrap-actions"
57

68
export default function() {
79
return {
@@ -15,11 +17,22 @@ export default function() {
1517
auth: {
1618
reducers,
1719
actions,
18-
selectors
20+
selectors,
21+
wrapActions: {
22+
authorize: wrappedAuthorizeAction,
23+
logout: wrappedLogoutAction,
24+
}
25+
},
26+
configs: {
27+
wrapActions: {
28+
loaded: wrappedLoadedAction,
29+
},
1930
},
2031
spec: {
21-
wrapActions: specWrapActionReplacements
22-
}
32+
wrapActions: {
33+
execute: wrappedExecuteAction,
34+
},
35+
},
2336
}
2437
}
2538
}

src/core/plugins/auth/wrap-actions.js

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @prettier
3+
*/
4+
5+
/**
6+
* `authorize` and `logout` wrapped actions provide capacity
7+
* to persist cookie based apiKey in document.cookie.
8+
*
9+
* `persistAuthorization` SwaggerUI options needs to set to `true`
10+
* for document.cookie persistence to work.
11+
*/
12+
export const authorize = (oriAction, system) => (payload) => {
13+
oriAction(payload)
14+
15+
const configs = system.getConfigs()
16+
17+
if (!configs.persistAuthorization) return
18+
19+
// create cookie
20+
try {
21+
const [{ schema, value }] = Object.values(payload)
22+
const isApiKeyAuth = schema.get("type") === "apiKey"
23+
const isInCookie = schema.get("in") === "cookie"
24+
const isApiKeyInCookie = isApiKeyAuth && isInCookie
25+
26+
if (isApiKeyInCookie) {
27+
document.cookie = `${schema.get("name")}=${value}; SameSite=None; Secure`
28+
}
29+
} catch (error) {
30+
console.error(
31+
"Error persisting cookie based apiKey in document.cookie.",
32+
error
33+
)
34+
}
35+
}
36+
37+
export const logout = (oriAction, system) => (payload) => {
38+
const configs = system.getConfigs()
39+
const authorized = system.authSelectors.authorized()
40+
41+
// deleting cookie
42+
try {
43+
if (configs.persistAuthorization && Array.isArray(payload)) {
44+
payload.forEach((authorizedName) => {
45+
const auth = authorized.get(authorizedName, {})
46+
const isApiKeyAuth = auth.getIn(["schema", "type"]) === "apiKey"
47+
const isInCookie = auth.getIn(["schema", "in"]) === "cookie"
48+
const isApiKeyInCookie = isApiKeyAuth && isInCookie
49+
50+
if (isApiKeyInCookie) {
51+
const cookieName = auth.getIn(["schema", "name"])
52+
document.cookie = `${cookieName}=; Max-Age=-99999999`
53+
}
54+
})
55+
}
56+
} catch (error) {
57+
console.error(
58+
"Error deleting cookie based apiKey from document.cookie.",
59+
error
60+
)
61+
}
62+
63+
oriAction(payload)
64+
}

src/core/plugins/configs/actions.js

+2-13
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,6 @@ export function toggle(configName) {
2121

2222

2323
// Hook
24-
export const loaded = () => ({getConfigs, authActions}) => {
25-
// check if we should restore authorization data from localStorage
26-
const configs = getConfigs()
27-
if (configs.persistAuthorization)
28-
{
29-
const authorized = localStorage.getItem("authorized")
30-
if(authorized)
31-
{
32-
authActions.restoreAuthorization({
33-
authorized: JSON.parse(authorized)
34-
})
35-
}
36-
}
24+
export const loaded = () => () => {
25+
// noop
3726
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { loaded } from "corePlugins/auth/configs-extensions/wrap-actions"
2+
3+
describe("loaded hook", () => {
4+
describe("authorization data restoration", () => {
5+
beforeEach(() => {
6+
localStorage.clear()
7+
})
8+
it("retrieve `authorized` value from `localStorage`", () => {
9+
const system = {
10+
getConfigs: () => ({
11+
persistAuthorization: true
12+
}),
13+
authActions: {
14+
15+
}
16+
}
17+
jest.spyOn(Object.getPrototypeOf(window.localStorage), "getItem")
18+
19+
loaded(jest.fn(), system)()
20+
expect(localStorage.getItem).toHaveBeenCalled()
21+
expect(localStorage.getItem).toHaveBeenCalledWith("authorized")
22+
})
23+
it("restore authorization data when a value exists", () => {
24+
const system = {
25+
getConfigs: () => ({
26+
persistAuthorization: true
27+
}),
28+
authActions: {
29+
restoreAuthorization: jest.fn(() => {})
30+
}
31+
}
32+
const mockData = {"api_key": {}}
33+
localStorage.setItem("authorized", JSON.stringify(mockData))
34+
loaded(jest.fn(), system)()
35+
expect(system.authActions.restoreAuthorization).toHaveBeenCalled()
36+
expect(system.authActions.restoreAuthorization).toHaveBeenCalledWith({
37+
authorized: mockData
38+
})
39+
})
40+
})
41+
})

test/unit/core/plugins/auth/wrap-spec-actions.js renamed to test/unit/core/plugins/auth/spec-extensions/wrap-actions.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
2-
import { execute } from "corePlugins/auth/spec-wrap-actions"
1+
import { execute } from "corePlugins/auth/spec-extensions/wrap-actions"
32

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

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* @prettier
3+
*/
4+
import { fromJS } from "immutable"
5+
import { authorize, logout } from "corePlugins/auth/wrap-actions"
6+
7+
describe("Cookie based apiKey persistence in document.cookie", () => {
8+
beforeEach(() => {
9+
let cookieJar = ""
10+
jest.spyOn(document, "cookie", "set").mockImplementation((cookie) => {
11+
cookieJar += cookie
12+
})
13+
jest.spyOn(document, "cookie", "get").mockImplementation(() => cookieJar)
14+
})
15+
16+
afterEach(() => {
17+
jest.restoreAllMocks()
18+
})
19+
20+
describe("given persistAuthorization=true", () => {
21+
it("should persist cookie in document.cookie", () => {
22+
const system = {
23+
getConfigs: () => ({
24+
persistAuthorization: true,
25+
}),
26+
}
27+
const payload = {
28+
api_key: {
29+
schema: fromJS({
30+
type: "apiKey",
31+
name: "apiKeyCookie",
32+
in: "cookie",
33+
}),
34+
value: "test",
35+
},
36+
}
37+
38+
authorize(jest.fn(), system)(payload)
39+
40+
expect(document.cookie).toEqual(
41+
"apiKeyCookie=test; SameSite=None; Secure"
42+
)
43+
})
44+
45+
it("should delete cookie from document.cookie", () => {
46+
const payload = fromJS({
47+
api_key: {
48+
schema: {
49+
type: "apiKey",
50+
name: "apiKeyCookie",
51+
in: "cookie",
52+
},
53+
value: "test",
54+
},
55+
})
56+
const system = {
57+
getConfigs: () => ({
58+
persistAuthorization: true,
59+
}),
60+
authSelectors: {
61+
authorized: () => payload,
62+
},
63+
}
64+
65+
logout(jest.fn(), system)(["api_key"])
66+
67+
expect(document.cookie).toEqual("apiKeyCookie=; Max-Age=-99999999")
68+
})
69+
})
70+
71+
describe("given persistAuthorization=false", () => {
72+
it("shouldn't persist cookie in document.cookie", () => {
73+
const system = {
74+
getConfigs: () => ({
75+
persistAuthorization: false,
76+
}),
77+
}
78+
const payload = {
79+
api_key: {
80+
schema: fromJS({
81+
type: "apiKey",
82+
name: "apiKeyCookie",
83+
in: "cookie",
84+
}),
85+
value: "test",
86+
},
87+
}
88+
89+
authorize(jest.fn(), system)(payload)
90+
91+
expect(document.cookie).toEqual("")
92+
})
93+
94+
it("should delete cookie from document.cookie", () => {
95+
const payload = fromJS({
96+
api_key: {
97+
schema: {
98+
type: "apiKey",
99+
name: "apiKeyCookie",
100+
in: "cookie",
101+
},
102+
value: "test",
103+
},
104+
})
105+
const system = {
106+
getConfigs: () => ({
107+
persistAuthorization: false,
108+
}),
109+
authSelectors: {
110+
authorized: () => payload,
111+
},
112+
}
113+
114+
logout(jest.fn(), system)(["api_key"])
115+
116+
expect(document.cookie).toEqual("")
117+
})
118+
})
119+
})
-40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { downloadConfig } from "corePlugins/configs/spec-actions"
2-
import { loaded } from "corePlugins/configs/actions"
32

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

@@ -23,43 +22,4 @@ describe("configs plugin - actions", () => {
2322
expect(fetchSpy).toHaveBeenCalledWith(req)
2423
})
2524
})
26-
27-
describe("loaded hook", () => {
28-
describe("authorization data restoration", () => {
29-
beforeEach(() => {
30-
localStorage.clear()
31-
})
32-
it("retrieve `authorized` value from `localStorage`", () => {
33-
const system = {
34-
getConfigs: () => ({
35-
persistAuthorization: true
36-
}),
37-
authActions: {
38-
39-
}
40-
}
41-
jest.spyOn(Object.getPrototypeOf(window.localStorage), "getItem")
42-
loaded()(system)
43-
expect(localStorage.getItem).toHaveBeenCalled()
44-
expect(localStorage.getItem).toHaveBeenCalledWith("authorized")
45-
})
46-
it("restore authorization data when a value exists", () => {
47-
const system = {
48-
getConfigs: () => ({
49-
persistAuthorization: true
50-
}),
51-
authActions: {
52-
restoreAuthorization: jest.fn(() => {})
53-
}
54-
}
55-
const mockData = {"api_key": {}}
56-
localStorage.setItem("authorized", JSON.stringify(mockData))
57-
loaded()(system)
58-
expect(system.authActions.restoreAuthorization).toHaveBeenCalled()
59-
expect(system.authActions.restoreAuthorization).toHaveBeenCalledWith({
60-
authorized: mockData
61-
})
62-
})
63-
})
64-
})
6525
})

0 commit comments

Comments
 (0)