diff --git a/docs/usage/configuration.md b/docs/usage/configuration.md
index 21533af7..1a26defc 100644
--- a/docs/usage/configuration.md
+++ b/docs/usage/configuration.md
@@ -84,6 +84,12 @@ Parameter name | Docker variable | Description
`modelPropertyMacro` | _Unavailable_ | `Function`. Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property is immutable
`parameterMacro` | _Unavailable_ | `Function`. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable
+##### Authorization
+
+Parameter name | Docker variable | Description
+--- | --- | -----
+`persistAuthorization` | _Unavailable_ | `Boolean=false`. If set to `true`, it persists authorization data and it would not be lost on browser close/refresh
+
### Instance methods
**💡 Take note! These are methods, not parameters**.
diff --git a/src/core/components/auth/auths.jsx b/src/core/components/auth/auths.jsx
index 9a9b0cdd..62a9bd61 100644
--- a/src/core/components/auth/auths.jsx
+++ b/src/core/components/auth/auths.jsx
@@ -27,7 +27,7 @@ export default class Auths extends React.Component {
e.preventDefault()
let { authActions } = this.props
- authActions.authorize(this.state)
+ authActions.authorizeWithPersistOption(this.state)
}
logoutClick =(e) => {
@@ -43,7 +43,7 @@ export default class Auths extends React.Component {
return prev
}, {}))
- authActions.logout(auths)
+ authActions.logoutWithPersistOption(auths)
}
close =(e) => {
diff --git a/src/core/components/auth/oauth2.jsx b/src/core/components/auth/oauth2.jsx
index efe02e4f..3dc042cb 100644
--- a/src/core/components/auth/oauth2.jsx
+++ b/src/core/components/auth/oauth2.jsx
@@ -96,7 +96,7 @@ export default class Oauth2 extends React.Component {
let { authActions, errActions, name } = this.props
errActions.clear({authId: name, type: "auth", source: "auth"})
- authActions.logout([ name ])
+ authActions.logoutWithPersistOption([ name ])
}
render() {
diff --git a/src/core/index.js b/src/core/index.js
index 05758621..109c70f9 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -37,6 +37,7 @@ export default function SwaggerUI(opts) {
filter: null,
validatorUrl: "https://validator.swagger.io/validator",
oauth2RedirectUrl: `${window.location.protocol}//${window.location.host}/oauth2-redirect.html`,
+ persistAuthorization: false,
configs: {},
custom: {},
displayOperationId: false,
diff --git a/src/core/plugins/auth/actions.js b/src/core/plugins/auth/actions.js
index f7c153c9..d3c4e3af 100644
--- a/src/core/plugins/auth/actions.js
+++ b/src/core/plugins/auth/actions.js
@@ -9,6 +9,7 @@ export const PRE_AUTHORIZE_OAUTH2 = "pre_authorize_oauth2"
export const AUTHORIZE_OAUTH2 = "authorize_oauth2"
export const VALIDATE = "validate"
export const CONFIGURE_AUTH = "configure_auth"
+export const RESTORE_AUTHORIZATION = "restore_authorization"
const scopeSeparator = " "
@@ -26,6 +27,11 @@ export function authorize(payload) {
}
}
+export const authorizeWithPersistOption = (payload) => ( { authActions } ) => {
+ authActions.authorize(payload)
+ authActions.persistAuthorizationIfNeeded()
+}
+
export function logout(payload) {
return {
type: LOGOUT,
@@ -33,6 +39,11 @@ export function logout(payload) {
}
}
+export const logoutWithPersistOption = (payload) => ( { authActions } ) => {
+ authActions.logout(payload)
+ authActions.persistAuthorizationIfNeeded()
+}
+
export const preAuthorizeImplicit = (payload) => ( { authActions, errActions } ) => {
let { auth , token, isValid } = payload
let { schema, name } = auth
@@ -60,9 +71,10 @@ export const preAuthorizeImplicit = (payload) => ( { authActions, errActions } )
return
}
- authActions.authorizeOauth2({ auth, token })
+ authActions.authorizeOauth2WithPersistOption({ auth, token })
}
+
export function authorizeOauth2(payload) {
return {
type: AUTHORIZE_OAUTH2,
@@ -70,6 +82,12 @@ export function authorizeOauth2(payload) {
}
}
+
+export const authorizeOauth2WithPersistOption = (payload) => ( { authActions } ) => {
+ authActions.authorizeOauth2(payload)
+ authActions.persistAuthorizationIfNeeded()
+}
+
export const authorizePassword = ( auth ) => ( { authActions } ) => {
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
let form = {
@@ -208,7 +226,7 @@ export const authorizeRequest = ( data ) => ( { fn, getConfigs, authActions, err
return
}
- authActions.authorizeOauth2({ auth, token})
+ authActions.authorizeOauth2WithPersistOption({ auth, token})
})
.catch(e => {
let err = new Error(e)
@@ -244,3 +262,19 @@ export function configureAuth(payload) {
payload: payload
}
}
+
+export function restoreAuthorization(payload) {
+ return {
+ type: RESTORE_AUTHORIZATION,
+ payload: payload
+ }
+}
+
+export const persistAuthorizationIfNeeded = () => ( { authSelectors, getConfigs } ) => {
+ const configs = getConfigs()
+ if (configs.persistAuthorization)
+ {
+ const authorized = authSelectors.authorized()
+ localStorage.setItem("authorized", JSON.stringify(authorized.toJS()))
+ }
+}
\ No newline at end of file
diff --git a/src/core/plugins/auth/reducers.js b/src/core/plugins/auth/reducers.js
index 756f1d14..5bfb7c1d 100644
--- a/src/core/plugins/auth/reducers.js
+++ b/src/core/plugins/auth/reducers.js
@@ -6,7 +6,8 @@ import {
AUTHORIZE,
AUTHORIZE_OAUTH2,
LOGOUT,
- CONFIGURE_AUTH
+ CONFIGURE_AUTH,
+ RESTORE_AUTHORIZATION
} from "./actions"
export default {
@@ -50,7 +51,10 @@ export default {
auth.token = Object.assign({}, token)
parsedAuth = fromJS(auth)
- return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
+ let map = state.get("authorized") || Map()
+ map = map.set(parsedAuth.get("name"), parsedAuth)
+
+ return state.set( "authorized", map )
},
[LOGOUT]: (state, { payload } ) =>{
@@ -65,5 +69,9 @@ export default {
[CONFIGURE_AUTH]: (state, { payload } ) =>{
return state.set("configs", payload)
- }
+ },
+
+ [RESTORE_AUTHORIZATION]: (state, { payload } ) =>{
+ return state.set("authorized", fromJS(payload.authorized))
+ },
}
diff --git a/src/core/plugins/configs/actions.js b/src/core/plugins/configs/actions.js
index 977407de..0c0ef0d9 100644
--- a/src/core/plugins/configs/actions.js
+++ b/src/core/plugins/configs/actions.js
@@ -21,4 +21,17 @@ export function toggle(configName) {
// Hook
-export const loaded = () => () => {}
+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)
+ })
+ }
+ }
+}
diff --git a/test/unit/core/plugins/auth/actions.js b/test/unit/core/plugins/auth/actions.js
index 9e29e010..349f19ee 100644
--- a/test/unit/core/plugins/auth/actions.js
+++ b/test/unit/core/plugins/auth/actions.js
@@ -1,5 +1,12 @@
-
-import { authorizeRequest, authorizeAccessCodeWithFormParams } from "corePlugins/auth/actions"
+import { Map } from "immutable"
+import {
+ authorizeRequest,
+ authorizeAccessCodeWithFormParams,
+ authorizeWithPersistOption,
+ authorizeOauth2WithPersistOption,
+ logoutWithPersistOption,
+ persistAuthorizationIfNeeded
+} from "corePlugins/auth/actions"
describe("auth plugin - actions", () => {
@@ -184,4 +191,123 @@ describe("auth plugin - actions", () => {
expect(actualArgument.body).toContain("grant_type=authorization_code")
})
})
+
+ describe("persistAuthorization", () => {
+ describe("wrapped functions with persist option", () => {
+ it("should wrap `authorize` action and persist data if needed", () => {
+
+ // Given
+ const data = {
+ "api_key": {}
+ }
+ const system = {
+ getConfigs: () => ({}),
+ authActions: {
+ authorize: jest.fn(()=>{}),
+ persistAuthorizationIfNeeded: jest.fn(()=>{})
+ }
+ }
+
+ // When
+ authorizeWithPersistOption(data)(system)
+
+ // Then
+ expect(system.authActions.authorize).toHaveBeenCalled()
+ expect(system.authActions.authorize).toHaveBeenCalledWith(data)
+ expect(system.authActions.persistAuthorizationIfNeeded).toHaveBeenCalled()
+ })
+
+ it("should wrap `oauth2Authorize` action and persist data if needed", () => {
+
+ // Given
+ const data = {
+ "api_key": {}
+ }
+ const system = {
+ getConfigs: () => ({}),
+ authActions: {
+ authorizeOauth2: jest.fn(()=>{}),
+ persistAuthorizationIfNeeded: jest.fn(()=>{})
+ }
+ }
+
+ // When
+ authorizeOauth2WithPersistOption(data)(system)
+
+ // Then
+ expect(system.authActions.authorizeOauth2).toHaveBeenCalled()
+ expect(system.authActions.authorizeOauth2).toHaveBeenCalledWith(data)
+ expect(system.authActions.persistAuthorizationIfNeeded).toHaveBeenCalled()
+ })
+
+ it("should wrap `logout` action and persist data if needed", () => {
+
+ // Given
+ const data = {
+ "api_key": {}
+ }
+ const system = {
+ getConfigs: () => ({}),
+ authActions: {
+ logout: jest.fn(()=>{}),
+ persistAuthorizationIfNeeded: jest.fn(()=>{})
+ }
+ }
+
+ // When
+ logoutWithPersistOption(data)(system)
+
+ // Then
+ expect(system.authActions.logout).toHaveBeenCalled()
+ expect(system.authActions.logout).toHaveBeenCalledWith(data)
+ expect(system.authActions.persistAuthorizationIfNeeded).toHaveBeenCalled()
+ })
+ })
+
+ describe("persistAuthorizationIfNeeded", () => {
+ beforeEach(() => {
+ localStorage.clear()
+ })
+ it("should skip if `persistAuthorization` is turned off", () => {
+ // Given
+ const system = {
+ getConfigs: () => ({
+ persistAuthorization: false
+ }),
+ authSelectors: {
+ authorized: jest.fn(()=>{})
+ }
+ }
+
+ // When
+ persistAuthorizationIfNeeded()(system)
+
+ // Then
+ expect(system.authSelectors.authorized).not.toHaveBeenCalled()
+ })
+ it("should persist authorization data to localStorage", () => {
+ // Given
+ const data = {
+ "api_key": {}
+ }
+ const system = {
+ getConfigs: () => ({
+ persistAuthorization: true
+ }),
+ authSelectors: {
+ authorized: jest.fn(()=>Map(data))
+ }
+ }
+ jest.spyOn(Object.getPrototypeOf(window.localStorage), "setItem")
+
+ // When
+ persistAuthorizationIfNeeded()(system)
+
+ expect(localStorage.setItem).toHaveBeenCalled()
+ expect(localStorage.setItem).toHaveBeenCalledWith("authorized", JSON.stringify(data))
+
+ })
+ })
+
+ })
})
diff --git a/test/unit/core/plugins/configs/actions.js b/test/unit/core/plugins/configs/actions.js
index b47d29fd..757c410d 100644
--- a/test/unit/core/plugins/configs/actions.js
+++ b/test/unit/core/plugins/configs/actions.js
@@ -1,5 +1,5 @@
-
import { downloadConfig } from "corePlugins/configs/spec-actions"
+import { loaded } from "corePlugins/configs/actions"
describe("configs plugin - actions", () => {
@@ -23,4 +23,43 @@ 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
+ })
+ })
+ })
+ })
})