@truedat/auth 4.48.0 → 4.48.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/package.json +3 -3
- package/src/groups/reducers/index.js +1 -1
- package/src/reducers/__tests__/authMessage.spec.js +10 -32
- package/src/reducers/authMessage.js +29 -6
- package/src/roles/components/PermissionGroup.js +4 -1
- package/src/sessions/api.js +2 -1
- package/src/sessions/components/PrivateRoute.js +10 -13
- package/src/sessions/components/UnauthorizedRoute.js +11 -19
- package/src/sessions/reducers/__tests__/authRedirect.spec.js +7 -13
- package/src/sessions/reducers/__tests__/authentication.spec.js +14 -21
- package/src/sessions/reducers/authRedirect.js +5 -1
- package/src/sessions/reducers/authentication.js +42 -60
- package/src/sessions/{routines/index.js → routines.js} +3 -0
- package/src/sessions/sagas/__tests__/login.spec.js +35 -37
- package/src/sessions/sagas/__tests__/logout.spec.js +14 -5
- package/src/sessions/sagas/__tests__/refresh.spec.js +87 -0
- package/src/sessions/sagas/__tests__/token.spec.js +19 -16
- package/src/sessions/sagas/index.js +5 -2
- package/src/sessions/sagas/login.js +24 -31
- package/src/sessions/sagas/logout.js +20 -5
- package/src/sessions/sagas/refresh.js +66 -0
- package/src/sessions/sagas/token.js +17 -13
- package/src/users/sagas/__tests__/fetchUsers.spec.js +3 -5
- package/src/sessions/actions.js +0 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.48.3] 2022-07-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- [TD-3614] Refresh access token before expiry
|
|
8
|
+
|
|
9
|
+
## [4.48.2] 2022-07-08
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- [TD-4995] Support localization of template fields and fixed values
|
|
14
|
+
|
|
3
15
|
## [4.45.4] 2022-06-03
|
|
4
16
|
|
|
5
17
|
### Fixed
|
|
6
18
|
|
|
7
|
-
- [TD-4873] Removed password and rep_password
|
|
19
|
+
- [TD-4873] Removed `password` and `rep_password` fields from `UserForm` user
|
|
20
|
+
update to avoid 403 error
|
|
8
21
|
|
|
9
22
|
## [4.44.4] 2022-05-19
|
|
10
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/auth",
|
|
3
|
-
"version": "4.48.
|
|
3
|
+
"version": "4.48.4",
|
|
4
4
|
"description": "Truedat Web Auth",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"jsnext:main": "src/index.js",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
]
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@truedat/core": "4.48.
|
|
90
|
+
"@truedat/core": "4.48.4",
|
|
91
91
|
"auth0-js": "^9.12.2",
|
|
92
92
|
"immutable": "^4.0.0-rc.12",
|
|
93
93
|
"jwt-decode": "^2.2.0",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"react-dom": ">= 16.8.6 < 17",
|
|
110
110
|
"semantic-ui-react": ">= 0.88.2 < 2.1"
|
|
111
111
|
},
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "5e50a034469bf3ad8e02a64eb875ad2b710b6b03"
|
|
113
113
|
}
|
|
@@ -11,72 +11,50 @@ describe("reducers: authMessage", () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it("should handle the login.TRIGGER action", () => {
|
|
14
|
-
expect(authMessage(fooState,
|
|
14
|
+
expect(authMessage(fooState, login())).toEqual(fooState);
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
it("should handle the login.FAILURE action", () => {
|
|
18
|
-
expect(
|
|
19
|
-
authMessage(fooState, {
|
|
20
|
-
type: login.FAILURE
|
|
21
|
-
})
|
|
22
|
-
).toEqual({
|
|
18
|
+
expect(authMessage(fooState, login.failure())).toEqual({
|
|
23
19
|
error: true,
|
|
24
20
|
icon: "attention",
|
|
25
21
|
header: "alert.login.failed.header",
|
|
26
|
-
content: "alert.login.failed.content"
|
|
22
|
+
content: "alert.login.failed.content",
|
|
27
23
|
});
|
|
28
24
|
});
|
|
29
25
|
|
|
30
26
|
it("should handle the login.SUCCESS action", () => {
|
|
31
|
-
expect(
|
|
32
|
-
authMessage(fooState, {
|
|
33
|
-
type: login.SUCCESS
|
|
34
|
-
})
|
|
35
|
-
).toEqual(initialState);
|
|
27
|
+
expect(authMessage(fooState, login.success())).toEqual(initialState);
|
|
36
28
|
});
|
|
37
29
|
|
|
38
30
|
it("should handle the login.SUCCESS action", () => {
|
|
39
|
-
expect(
|
|
40
|
-
authMessage(fooState, {
|
|
41
|
-
type: login.SUCCESS
|
|
42
|
-
})
|
|
43
|
-
).toEqual(initialState);
|
|
31
|
+
expect(authMessage(fooState, login.success())).toEqual(initialState);
|
|
44
32
|
});
|
|
45
33
|
|
|
46
34
|
it("should handle the auth0Login.SUCCESS action", () => {
|
|
47
|
-
expect(
|
|
48
|
-
authMessage(fooState, {
|
|
49
|
-
type: auth0Login.SUCCESS
|
|
50
|
-
})
|
|
51
|
-
).toEqual(initialState);
|
|
35
|
+
expect(authMessage(fooState, auth0Login.success())).toEqual(initialState);
|
|
52
36
|
});
|
|
53
37
|
|
|
54
38
|
it("should handle the updatePassword.FAILURE status ", () => {
|
|
55
39
|
expect(
|
|
56
|
-
authMessage(fooState,
|
|
57
|
-
type: updatePassword.FAILURE,
|
|
58
|
-
payload: "Failed with xxx"
|
|
59
|
-
})
|
|
40
|
+
authMessage(fooState, updatePassword.failure("Failed with xxx"))
|
|
60
41
|
).toEqual({
|
|
61
42
|
error: true,
|
|
62
43
|
header: "user.password.error.xxx.header",
|
|
63
44
|
content: "user.password.error.xxx.content",
|
|
64
|
-
icon: "attention"
|
|
45
|
+
icon: "attention",
|
|
65
46
|
});
|
|
66
47
|
});
|
|
67
48
|
|
|
68
49
|
it("should handle the updatePassword.SUCCESS with OK payload action", () => {
|
|
69
50
|
expect(
|
|
70
|
-
authMessage(fooState, {
|
|
71
|
-
type: updatePassword.SUCCESS,
|
|
72
|
-
payload: { foo: "bar" }
|
|
73
|
-
})
|
|
51
|
+
authMessage(fooState, updatePassword.success({ foo: "bar" }))
|
|
74
52
|
).toEqual({
|
|
75
53
|
error: false,
|
|
76
54
|
header: "user.password.change",
|
|
77
55
|
content: "user.password.success.change",
|
|
78
56
|
icon: "check",
|
|
79
|
-
color: "green"
|
|
57
|
+
color: "green",
|
|
80
58
|
});
|
|
81
59
|
});
|
|
82
60
|
});
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { dismissAlert } from "@truedat/core/routines";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
login,
|
|
4
|
+
auth0Login,
|
|
5
|
+
openIdLogin,
|
|
6
|
+
nonceLogin,
|
|
7
|
+
updatePassword,
|
|
8
|
+
} from "../routines";
|
|
3
9
|
|
|
4
10
|
const initialState = {};
|
|
5
11
|
|
|
@@ -14,27 +20,44 @@ export const authMessage = (state = initialState, foo) => {
|
|
|
14
20
|
header: "alert.auth0.failed.header",
|
|
15
21
|
content: "alert.auth0.failed.content",
|
|
16
22
|
icon: "attention",
|
|
17
|
-
text: payload
|
|
23
|
+
text: payload,
|
|
18
24
|
};
|
|
19
25
|
case login.FAILURE:
|
|
20
26
|
return {
|
|
21
27
|
error: true,
|
|
22
28
|
header: "alert.login.failed.header",
|
|
23
29
|
content: "alert.login.failed.content",
|
|
24
|
-
icon: "attention"
|
|
30
|
+
icon: "attention",
|
|
31
|
+
};
|
|
32
|
+
case openIdLogin.FAILURE:
|
|
33
|
+
return {
|
|
34
|
+
error: true,
|
|
35
|
+
header: "alert.login.failed.header",
|
|
36
|
+
content: "alert.login.failed.content",
|
|
37
|
+
icon: "attention",
|
|
38
|
+
};
|
|
39
|
+
case nonceLogin.FAILURE:
|
|
40
|
+
return {
|
|
41
|
+
error: true,
|
|
42
|
+
header: "alert.login.failed.header",
|
|
43
|
+
content: "alert.login.failed.content",
|
|
44
|
+
icon: "attention",
|
|
25
45
|
};
|
|
26
46
|
case login.SUCCESS:
|
|
27
47
|
return initialState;
|
|
28
48
|
case auth0Login.SUCCESS:
|
|
29
49
|
return initialState;
|
|
50
|
+
case openIdLogin.SUCCESS:
|
|
51
|
+
return initialState;
|
|
52
|
+
case nonceLogin.SUCCESS:
|
|
53
|
+
return initialState;
|
|
30
54
|
case updatePassword.FAILURE:
|
|
31
55
|
const status = payload.substr(payload.length - 3);
|
|
32
|
-
|
|
33
56
|
return {
|
|
34
57
|
error: true,
|
|
35
58
|
header: `user.password.error.${status}.header`,
|
|
36
59
|
content: `user.password.error.${status}.content`,
|
|
37
|
-
icon: "attention"
|
|
60
|
+
icon: "attention",
|
|
38
61
|
};
|
|
39
62
|
case updatePassword.SUCCESS:
|
|
40
63
|
return {
|
|
@@ -42,7 +65,7 @@ export const authMessage = (state = initialState, foo) => {
|
|
|
42
65
|
header: "user.password.change",
|
|
43
66
|
content: "user.password.success.change",
|
|
44
67
|
icon: "check",
|
|
45
|
-
color: "green"
|
|
68
|
+
color: "green",
|
|
46
69
|
};
|
|
47
70
|
default:
|
|
48
71
|
return state;
|
|
@@ -25,7 +25,10 @@ export const PermissionGroup = ({
|
|
|
25
25
|
<List.Item key={i}>
|
|
26
26
|
<Checkbox
|
|
27
27
|
label={{
|
|
28
|
-
children: formatMessage({
|
|
28
|
+
children: formatMessage({
|
|
29
|
+
id: `permission.${p.name}`,
|
|
30
|
+
defaultMessage: p.name,
|
|
31
|
+
}),
|
|
29
32
|
}}
|
|
30
33
|
checked={_.any(_.propEq("id", p.id))(rolePermissions)}
|
|
31
34
|
onChange={(e, data) => {
|
package/src/sessions/api.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import PropTypes from "prop-types";
|
|
3
4
|
import { compose } from "redux";
|
|
4
5
|
import { connect } from "react-redux";
|
|
5
6
|
import { Route, Redirect, withRouter } from "react-router-dom";
|
|
6
7
|
import { LOGIN, UNAUTHORIZED } from "@truedat/core/routes";
|
|
7
|
-
import {
|
|
8
|
+
import { retrieveToken } from "../routines";
|
|
8
9
|
|
|
9
10
|
class PrivateRoute extends React.Component {
|
|
10
11
|
static propTypes = {
|
|
11
12
|
token: PropTypes.string,
|
|
12
13
|
component: PropTypes.func,
|
|
13
|
-
|
|
14
|
+
hasPermissions: PropTypes.bool,
|
|
14
15
|
location: PropTypes.object,
|
|
15
|
-
|
|
16
|
+
retrieveToken: PropTypes.func,
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
componentDidMount() {
|
|
19
|
-
const { token,
|
|
20
|
+
const { token, retrieveToken } = this.props;
|
|
20
21
|
if (token === undefined) {
|
|
21
|
-
|
|
22
|
+
retrieveToken();
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -26,7 +27,7 @@ class PrivateRoute extends React.Component {
|
|
|
26
27
|
const {
|
|
27
28
|
component: Component,
|
|
28
29
|
token,
|
|
29
|
-
|
|
30
|
+
hasPermissions,
|
|
30
31
|
location,
|
|
31
32
|
...rest
|
|
32
33
|
} = this.props;
|
|
@@ -35,7 +36,7 @@ class PrivateRoute extends React.Component {
|
|
|
35
36
|
<Route
|
|
36
37
|
{...rest}
|
|
37
38
|
render={(props) => {
|
|
38
|
-
if (token && !
|
|
39
|
+
if (token && !hasPermissions) {
|
|
39
40
|
return (
|
|
40
41
|
<Redirect
|
|
41
42
|
to={{
|
|
@@ -59,14 +60,10 @@ class PrivateRoute extends React.Component {
|
|
|
59
60
|
|
|
60
61
|
const mapStateToProps = ({ authentication }) => ({
|
|
61
62
|
token: authentication.token,
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const mapDispatchToProps = (dispatch) => ({
|
|
66
|
-
readTokenFromStorage: () => dispatch({ type: REQUEST_TOKEN }),
|
|
63
|
+
hasPermissions: !_.isEmpty(authentication.entitlements),
|
|
67
64
|
});
|
|
68
65
|
|
|
69
66
|
export default compose(
|
|
70
67
|
withRouter,
|
|
71
|
-
connect(mapStateToProps,
|
|
68
|
+
connect(mapStateToProps, { retrieveToken })
|
|
72
69
|
)(PrivateRoute);
|
|
@@ -1,37 +1,33 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import PropTypes from "prop-types";
|
|
3
4
|
import { connect } from "react-redux";
|
|
4
5
|
import { Route, Redirect } from "react-router-dom";
|
|
5
6
|
import { LOGIN } from "@truedat/core/routes";
|
|
6
|
-
import {
|
|
7
|
+
import { retrieveToken } from "../routines";
|
|
7
8
|
|
|
8
9
|
class UnauthorizedRoute extends React.Component {
|
|
9
10
|
static propTypes = {
|
|
10
11
|
token: PropTypes.string,
|
|
11
12
|
component: PropTypes.func,
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
retrieveToken: PropTypes.func,
|
|
14
|
+
hasPermissions: PropTypes.bool,
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
componentDidMount() {
|
|
17
|
-
const { token,
|
|
18
|
+
const { token, retrieveToken } = this.props;
|
|
18
19
|
if (token === undefined) {
|
|
19
|
-
|
|
20
|
+
retrieveToken();
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
render() {
|
|
24
|
-
const {
|
|
25
|
-
component: Component,
|
|
26
|
-
token,
|
|
27
|
-
has_permissions,
|
|
28
|
-
...rest
|
|
29
|
-
} = this.props;
|
|
25
|
+
const { component: Component, token, hasPermissions, ...rest } = this.props;
|
|
30
26
|
return (
|
|
31
27
|
<Route
|
|
32
28
|
{...rest}
|
|
33
|
-
render={props => {
|
|
34
|
-
if (token && !
|
|
29
|
+
render={(props) => {
|
|
30
|
+
if (token && !hasPermissions) {
|
|
35
31
|
return <Component {...props} />;
|
|
36
32
|
}
|
|
37
33
|
return <Redirect to={{ pathname: LOGIN }} />;
|
|
@@ -43,11 +39,7 @@ class UnauthorizedRoute extends React.Component {
|
|
|
43
39
|
|
|
44
40
|
const mapStateToProps = ({ authentication }) => ({
|
|
45
41
|
token: authentication.token,
|
|
46
|
-
|
|
42
|
+
hasPermissions: !_.isEmpty(authentication.entitlements),
|
|
47
43
|
});
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
readTokenFromStorage: () => dispatch({ type: REQUEST_TOKEN })
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
export default connect(mapStateToProps, mapDispatchToProps)(UnauthorizedRoute);
|
|
45
|
+
export default connect(mapStateToProps, { retrieveToken })(UnauthorizedRoute);
|
|
@@ -11,36 +11,30 @@ describe("reducers: authRedirect", () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it("should handle the clearRedirect.TRIGGER action", () => {
|
|
14
|
-
expect(authRedirect(fooState,
|
|
15
|
-
initialState
|
|
16
|
-
);
|
|
14
|
+
expect(authRedirect(fooState, clearRedirect())).toEqual(initialState);
|
|
17
15
|
});
|
|
18
16
|
|
|
19
17
|
it("should handle the auth0Login.FAILURE action", () => {
|
|
20
|
-
expect(authRedirect(fooState,
|
|
21
|
-
"/login"
|
|
22
|
-
);
|
|
18
|
+
expect(authRedirect(fooState, auth0Login.failure())).toEqual("/login");
|
|
23
19
|
});
|
|
24
20
|
|
|
25
21
|
it("should handle the auth0Login.SUCCESS action", () => {
|
|
26
|
-
expect(authRedirect(fooState,
|
|
22
|
+
expect(authRedirect(fooState, auth0Login.success())).toEqual("");
|
|
27
23
|
});
|
|
28
24
|
|
|
29
25
|
it("should handle the openIdLogin.FAILURE action", () => {
|
|
30
|
-
expect(authRedirect(fooState,
|
|
31
|
-
"/login"
|
|
32
|
-
);
|
|
26
|
+
expect(authRedirect(fooState, openIdLogin.failure())).toEqual("/login");
|
|
33
27
|
});
|
|
34
28
|
|
|
35
29
|
it("should handle the openIdLogin.SUCCESS action", () => {
|
|
36
|
-
expect(authRedirect(fooState,
|
|
30
|
+
expect(authRedirect(fooState, openIdLogin.success())).toEqual("");
|
|
37
31
|
});
|
|
38
32
|
|
|
39
|
-
it("should handle FAILURES with status 401
|
|
33
|
+
it("should handle FAILURES with status 401 status and unauthorized message", () => {
|
|
40
34
|
expect(
|
|
41
35
|
authRedirect(fooState, {
|
|
42
36
|
type: "random/FAILURE",
|
|
43
|
-
payload: { status: 401 },
|
|
37
|
+
payload: { status: 401, data: { message: "unauthorized" } },
|
|
44
38
|
})
|
|
45
39
|
).toEqual("/login");
|
|
46
40
|
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
authentication,
|
|
3
|
+
loginSubmitting,
|
|
4
|
+
initialState,
|
|
5
|
+
} from "../authentication";
|
|
6
|
+
import { login, logout, retrieveToken } from "../../routines";
|
|
4
7
|
|
|
5
8
|
const fooState = { foo: "bar" };
|
|
6
9
|
|
|
@@ -23,44 +26,34 @@ describe("reducers: loginSubmitting", () => {
|
|
|
23
26
|
});
|
|
24
27
|
|
|
25
28
|
describe("reducers: authentication", () => {
|
|
26
|
-
const initialState = {
|
|
27
|
-
token: undefined,
|
|
28
|
-
role: undefined,
|
|
29
|
-
has_permissions: false,
|
|
30
|
-
groups: []
|
|
31
|
-
};
|
|
32
29
|
it("should provide the initial state", () => {
|
|
33
|
-
expect(authentication(undefined, {})).
|
|
30
|
+
expect(authentication(undefined, {})).toBe(initialState);
|
|
34
31
|
});
|
|
35
32
|
|
|
36
33
|
it("should handle the SUCCESS action", () => {
|
|
37
34
|
const payload = { token: "TOKEN", refresh_token: "REFRESH" };
|
|
38
35
|
expect(
|
|
39
|
-
authentication({ foo: "bar" },
|
|
36
|
+
authentication({ foo: "bar" }, login.success(payload))
|
|
40
37
|
).toMatchObject({ token: "TOKEN" });
|
|
41
38
|
});
|
|
42
39
|
|
|
43
40
|
it("should handle the FAILURE action", () => {
|
|
44
|
-
expect(authentication({ foo: "bar" },
|
|
45
|
-
initialState
|
|
46
|
-
);
|
|
41
|
+
expect(authentication({ foo: "bar" }, login.failure())).toBe(initialState);
|
|
47
42
|
});
|
|
48
43
|
|
|
49
|
-
it("should handle the
|
|
50
|
-
expect(authentication({ foo: "bar" },
|
|
51
|
-
initialState
|
|
52
|
-
);
|
|
44
|
+
it("should handle the logout.FULFILL action", () => {
|
|
45
|
+
expect(authentication({ foo: "bar" }, logout.fulfill())).toBe(initialState);
|
|
53
46
|
});
|
|
54
47
|
|
|
55
|
-
it("should handle the
|
|
48
|
+
it("should handle the retrieveToken.SUCCESS action", () => {
|
|
56
49
|
expect(
|
|
57
|
-
authentication({ foo: "bar" }, {
|
|
50
|
+
authentication({ foo: "bar" }, retrieveToken.success({ token: "token" }))
|
|
58
51
|
).toHaveProperty("token");
|
|
59
52
|
});
|
|
60
53
|
|
|
61
54
|
it("should ignore unknown actions", () => {
|
|
62
55
|
expect(authentication({ foo: "bar" }, { type: "WTF" })).toEqual({
|
|
63
|
-
foo: "bar"
|
|
56
|
+
foo: "bar",
|
|
64
57
|
});
|
|
65
58
|
});
|
|
66
59
|
});
|
|
@@ -21,7 +21,11 @@ const authRedirect = (state = initialState, { type, payload }) => {
|
|
|
21
21
|
case nonceLogin.SUCCESS:
|
|
22
22
|
return "/";
|
|
23
23
|
default:
|
|
24
|
-
if (
|
|
24
|
+
if (
|
|
25
|
+
_.endsWith("/FAILURE")(type) &&
|
|
26
|
+
payload?.status === 401 &&
|
|
27
|
+
payload?.data?.message === "unauthorized"
|
|
28
|
+
) {
|
|
25
29
|
return "/login";
|
|
26
30
|
}
|
|
27
31
|
return state;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
login,
|
|
4
|
+
auth0Login,
|
|
5
|
+
openIdLogin,
|
|
6
|
+
nonceLogin,
|
|
7
|
+
logout,
|
|
8
|
+
} from "../routines";
|
|
9
|
+
import { retrieveToken } from "../routines";
|
|
4
10
|
|
|
5
11
|
const loginSubmitting = (state = false, { type }) => {
|
|
6
12
|
switch (type) {
|
|
@@ -13,80 +19,56 @@ const loginSubmitting = (state = false, { type }) => {
|
|
|
13
19
|
}
|
|
14
20
|
};
|
|
15
21
|
|
|
16
|
-
const initialState = {
|
|
22
|
+
export const initialState = {
|
|
17
23
|
auth_realm: undefined,
|
|
18
24
|
token: undefined,
|
|
19
25
|
role: undefined,
|
|
20
|
-
|
|
26
|
+
entitlements: undefined,
|
|
21
27
|
groups: [],
|
|
22
28
|
};
|
|
23
29
|
|
|
24
|
-
const
|
|
30
|
+
const TOKEN_PROPS = [
|
|
31
|
+
"amr",
|
|
32
|
+
"groups",
|
|
33
|
+
"entitlements",
|
|
34
|
+
"role",
|
|
35
|
+
"token",
|
|
36
|
+
"user_name",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const authentication = (state = initialState, { type, payload }) => {
|
|
25
40
|
switch (type) {
|
|
26
41
|
case login.TRIGGER:
|
|
27
42
|
const { auth_realm } = payload;
|
|
28
43
|
return { ...initialState, auth_realm };
|
|
29
|
-
case openIdLogin.SUCCESS:
|
|
30
|
-
return Object.assign(
|
|
31
|
-
{},
|
|
32
|
-
state,
|
|
33
|
-
_.pick([
|
|
34
|
-
"user_name",
|
|
35
|
-
"token",
|
|
36
|
-
"role",
|
|
37
|
-
"has_permissions",
|
|
38
|
-
"remember",
|
|
39
|
-
"access_method",
|
|
40
|
-
"groups",
|
|
41
|
-
])(payload)
|
|
42
|
-
);
|
|
43
44
|
case auth0Login.SUCCESS:
|
|
44
|
-
return Object.assign(
|
|
45
|
-
{},
|
|
46
|
-
state,
|
|
47
|
-
_.pick([
|
|
48
|
-
"user_name",
|
|
49
|
-
"token",
|
|
50
|
-
"role",
|
|
51
|
-
"has_permissions",
|
|
52
|
-
"remember",
|
|
53
|
-
"access_method",
|
|
54
|
-
"groups",
|
|
55
|
-
])(payload)
|
|
56
|
-
);
|
|
45
|
+
return Object.assign({}, state, _.pick(TOKEN_PROPS)(payload));
|
|
57
46
|
case login.SUCCESS:
|
|
58
|
-
return Object.assign(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"access_method",
|
|
68
|
-
"groups",
|
|
69
|
-
])(payload)
|
|
70
|
-
);
|
|
47
|
+
return Object.assign({}, state, _.pick(TOKEN_PROPS)(payload));
|
|
48
|
+
case nonceLogin.SUCCESS:
|
|
49
|
+
return Object.assign({}, state, _.pick(TOKEN_PROPS)(payload));
|
|
50
|
+
case openIdLogin.SUCCESS:
|
|
51
|
+
return Object.assign({}, state, _.pick(TOKEN_PROPS)(payload));
|
|
52
|
+
case retrieveToken.SUCCESS:
|
|
53
|
+
return Object.assign({}, state, _.pick(TOKEN_PROPS)(payload));
|
|
54
|
+
case auth0Login.FAILURE:
|
|
55
|
+
return initialState;
|
|
71
56
|
case login.FAILURE:
|
|
72
57
|
return initialState;
|
|
73
|
-
case
|
|
74
|
-
return
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"user_name",
|
|
79
|
-
"token",
|
|
80
|
-
"role",
|
|
81
|
-
"has_permissions",
|
|
82
|
-
"remember",
|
|
83
|
-
"access_method",
|
|
84
|
-
"groups",
|
|
85
|
-
])(data)
|
|
86
|
-
);
|
|
87
|
-
case RECEIVE_LOGOUT:
|
|
58
|
+
case nonceLogin.FAILURE:
|
|
59
|
+
return initialState;
|
|
60
|
+
case openIdLogin.FAILURE:
|
|
61
|
+
return initialState;
|
|
62
|
+
case logout.FULFILL:
|
|
88
63
|
return initialState;
|
|
89
64
|
default:
|
|
65
|
+
if (
|
|
66
|
+
_.endsWith("/FAILURE")(type) &&
|
|
67
|
+
payload?.status === 401 &&
|
|
68
|
+
payload?.data?.message === "unauthorized"
|
|
69
|
+
) {
|
|
70
|
+
return initialState;
|
|
71
|
+
}
|
|
90
72
|
return state;
|
|
91
73
|
}
|
|
92
74
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createRoutine } from "redux-saga-routines";
|
|
2
2
|
|
|
3
3
|
export const login = createRoutine("LOGIN");
|
|
4
|
+
export const logout = createRoutine("LOGOUT");
|
|
4
5
|
export const auth0Login = createRoutine("AUTH0");
|
|
5
6
|
export const openIdLogin = createRoutine("OPENID");
|
|
6
7
|
export const nonceLogin = createRoutine("NONCE");
|
|
7
8
|
export const fetchAuthMethods = createRoutine("FETCH_AUTH_METHODS");
|
|
9
|
+
export const refreshSession = createRoutine("REFRESH_SESSION");
|
|
10
|
+
export const retrieveToken = createRoutine("READ_TOKEN");
|
|
@@ -38,17 +38,17 @@ describe("sagas: post login", () => {
|
|
|
38
38
|
it("should handle postLoginSaga when a response is returned", () => {
|
|
39
39
|
const user_name = "fulanito.menganito@bluetab.net";
|
|
40
40
|
const password = "top_secret";
|
|
41
|
-
const access_method = "
|
|
41
|
+
const access_method = "pwd";
|
|
42
42
|
const token = "token";
|
|
43
|
-
const
|
|
43
|
+
const entitlements = ["p"];
|
|
44
44
|
const token_username = "some_username";
|
|
45
45
|
const role = "user";
|
|
46
|
-
const type = "type";
|
|
47
46
|
const exp = "exp";
|
|
48
47
|
const pre_user = { user_name, password };
|
|
49
48
|
const user = { user_name, password };
|
|
50
49
|
const data = { token };
|
|
51
50
|
const groups = ["foo"];
|
|
51
|
+
const amr = ["pwd"];
|
|
52
52
|
|
|
53
53
|
expect(() => {
|
|
54
54
|
testSaga(postLoginSaga, { payload: pre_user, access_method })
|
|
@@ -61,24 +61,22 @@ describe("sagas: post login", () => {
|
|
|
61
61
|
.next()
|
|
62
62
|
.call(jwt_decode, token)
|
|
63
63
|
.next({
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
has_permissions,
|
|
67
|
-
access_method,
|
|
68
|
-
type,
|
|
64
|
+
amr,
|
|
65
|
+
entitlements,
|
|
69
66
|
exp,
|
|
70
67
|
groups,
|
|
68
|
+
role,
|
|
69
|
+
user_name: token_username,
|
|
71
70
|
})
|
|
72
71
|
.put(
|
|
73
72
|
login.success({
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
role,
|
|
77
|
-
has_permissions,
|
|
78
|
-
access_method,
|
|
79
|
-
type,
|
|
73
|
+
amr,
|
|
74
|
+
entitlements,
|
|
80
75
|
exp,
|
|
81
76
|
groups,
|
|
77
|
+
role,
|
|
78
|
+
token,
|
|
79
|
+
user_name: token_username,
|
|
82
80
|
})
|
|
83
81
|
)
|
|
84
82
|
.next()
|
|
@@ -95,7 +93,7 @@ describe("sagas: post login", () => {
|
|
|
95
93
|
const user = { user_name: username, password };
|
|
96
94
|
const errorData = "Login failed";
|
|
97
95
|
const error = { response: { data: errorData } };
|
|
98
|
-
const access_method = "
|
|
96
|
+
const access_method = "pwd";
|
|
99
97
|
|
|
100
98
|
expect(() => {
|
|
101
99
|
testSaga(postLoginSaga, { payload: user })
|
|
@@ -119,10 +117,10 @@ describe("sagas: openIdLogin", () => {
|
|
|
119
117
|
const state = "bar";
|
|
120
118
|
const payload = { code, state };
|
|
121
119
|
|
|
120
|
+
const amr = [];
|
|
122
121
|
const user_name = "some_username";
|
|
123
122
|
const role = "user";
|
|
124
|
-
const
|
|
125
|
-
const type = "type";
|
|
123
|
+
const entitlements = ["p"];
|
|
126
124
|
const exp = "exp";
|
|
127
125
|
const groups = ["foo"];
|
|
128
126
|
const token = "token";
|
|
@@ -140,22 +138,22 @@ describe("sagas: openIdLogin", () => {
|
|
|
140
138
|
.next()
|
|
141
139
|
.call(jwt_decode, token)
|
|
142
140
|
.next({
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
has_permissions,
|
|
146
|
-
type,
|
|
141
|
+
amr,
|
|
142
|
+
entitlements,
|
|
147
143
|
exp,
|
|
148
144
|
groups,
|
|
145
|
+
role,
|
|
146
|
+
user_name,
|
|
149
147
|
})
|
|
150
148
|
.put(
|
|
151
149
|
openIdLogin.success({
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
role,
|
|
155
|
-
has_permissions,
|
|
156
|
-
type,
|
|
150
|
+
amr,
|
|
151
|
+
entitlements,
|
|
157
152
|
exp,
|
|
158
153
|
groups,
|
|
154
|
+
role,
|
|
155
|
+
token,
|
|
156
|
+
user_name,
|
|
159
157
|
})
|
|
160
158
|
)
|
|
161
159
|
.next()
|
|
@@ -170,10 +168,10 @@ describe("sagas: openIdLogin", () => {
|
|
|
170
168
|
const state = "bar";
|
|
171
169
|
const payload = { id_token, state };
|
|
172
170
|
|
|
171
|
+
const amr = [];
|
|
173
172
|
const user_name = "some_username";
|
|
174
173
|
const role = "user";
|
|
175
|
-
const
|
|
176
|
-
const type = "type";
|
|
174
|
+
const entitlements = [];
|
|
177
175
|
const exp = "exp";
|
|
178
176
|
const groups = ["foo"];
|
|
179
177
|
const token = "token";
|
|
@@ -196,22 +194,22 @@ describe("sagas: openIdLogin", () => {
|
|
|
196
194
|
.next()
|
|
197
195
|
.call(jwt_decode, token)
|
|
198
196
|
.next({
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
has_permissions,
|
|
202
|
-
type,
|
|
197
|
+
amr,
|
|
198
|
+
entitlements,
|
|
203
199
|
exp,
|
|
204
200
|
groups,
|
|
201
|
+
role,
|
|
202
|
+
user_name,
|
|
205
203
|
})
|
|
206
204
|
.put(
|
|
207
205
|
openIdLogin.success({
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
role,
|
|
211
|
-
has_permissions,
|
|
212
|
-
type,
|
|
206
|
+
amr,
|
|
207
|
+
entitlements,
|
|
213
208
|
exp,
|
|
214
209
|
groups,
|
|
210
|
+
role,
|
|
211
|
+
token,
|
|
212
|
+
user_name,
|
|
215
213
|
})
|
|
216
214
|
)
|
|
217
215
|
.next()
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { testSaga } from "redux-saga-test-plan";
|
|
2
|
+
import { apiJsonDelete, JSON_OPTS } from "@truedat/core/services/api";
|
|
2
3
|
import { clearToken } from "@truedat/core/services/storage";
|
|
4
|
+
import { API_SESSIONS } from "../../api";
|
|
3
5
|
import { logoutSaga, logoutRequestSaga } from "../logout";
|
|
4
|
-
import {
|
|
6
|
+
import { logout } from "../../routines";
|
|
5
7
|
|
|
6
8
|
describe("sagas: logout request", () => {
|
|
7
|
-
it("should success handling
|
|
9
|
+
it("should success handling logoutRequestSaga", () => {
|
|
8
10
|
expect(() => {
|
|
9
11
|
testSaga(logoutRequestSaga)
|
|
10
12
|
.next()
|
|
11
|
-
.takeLatest(
|
|
13
|
+
.takeLatest(logout.TRIGGER, logoutSaga)
|
|
12
14
|
.finish()
|
|
13
15
|
.isDone();
|
|
14
16
|
}).not.toThrow();
|
|
@@ -22,13 +24,20 @@ describe("sagas: logout request", () => {
|
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
describe("sagas: logoutSaga", () => {
|
|
25
|
-
it("should call clearToken", () => {
|
|
27
|
+
it("should call DELETE /api/sessions and clearToken", () => {
|
|
28
|
+
const data = "";
|
|
26
29
|
expect(() => {
|
|
27
30
|
testSaga(logoutSaga)
|
|
31
|
+
.next()
|
|
32
|
+
.put(logout.request())
|
|
33
|
+
.next()
|
|
34
|
+
.call(apiJsonDelete, API_SESSIONS, JSON_OPTS)
|
|
35
|
+
.next({ data })
|
|
36
|
+
.put(logout.success(data))
|
|
28
37
|
.next()
|
|
29
38
|
.call(clearToken)
|
|
30
39
|
.next()
|
|
31
|
-
.put(
|
|
40
|
+
.put(logout.fulfill())
|
|
32
41
|
.next()
|
|
33
42
|
.isDone();
|
|
34
43
|
}).not.toThrow();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import jwt_decode from "jwt-decode";
|
|
2
|
+
import { testSaga } from "redux-saga-test-plan";
|
|
3
|
+
import { takeLatest } from "redux-saga/effects";
|
|
4
|
+
import { apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
|
|
5
|
+
import { saveToken } from "@truedat/core/services/storage";
|
|
6
|
+
import { API_SESSIONS_REFRESH } from "../../api";
|
|
7
|
+
import { refreshDelay, refreshSaga, refreshRequestSaga } from "../refresh";
|
|
8
|
+
import {
|
|
9
|
+
login,
|
|
10
|
+
auth0Login,
|
|
11
|
+
openIdLogin,
|
|
12
|
+
nonceLogin,
|
|
13
|
+
refreshSession,
|
|
14
|
+
retrieveToken,
|
|
15
|
+
} from "../../routines";
|
|
16
|
+
|
|
17
|
+
describe("sagas: refresh request", () => {
|
|
18
|
+
it(`should success handling login success, etc.`, () => {
|
|
19
|
+
expect(() => {
|
|
20
|
+
testSaga(refreshRequestSaga)
|
|
21
|
+
.next()
|
|
22
|
+
.all([
|
|
23
|
+
takeLatest(login.SUCCESS, refreshSaga),
|
|
24
|
+
takeLatest(auth0Login.SUCCESS, refreshSaga),
|
|
25
|
+
takeLatest(openIdLogin.SUCCESS, refreshSaga),
|
|
26
|
+
takeLatest(nonceLogin.SUCCESS, refreshSaga),
|
|
27
|
+
takeLatest(refreshSession.SUCCESS, refreshSaga),
|
|
28
|
+
takeLatest(retrieveToken.SUCCESS, refreshSaga),
|
|
29
|
+
])
|
|
30
|
+
.finish()
|
|
31
|
+
.isDone();
|
|
32
|
+
}).not.toThrow();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should fail handling refreshRequestSaga if wrong pattern", () => {
|
|
36
|
+
expect(() => {
|
|
37
|
+
testSaga(refreshRequestSaga)
|
|
38
|
+
.next()
|
|
39
|
+
.takeLatest("WTF_PATTERN", refreshSaga);
|
|
40
|
+
}).toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("sagas: refreshSaga", () => {
|
|
45
|
+
it("should call POST /api/sessions/refresh and saveToken", () => {
|
|
46
|
+
const amr = "pwd";
|
|
47
|
+
const entitlements = ["p"];
|
|
48
|
+
const groups = [];
|
|
49
|
+
const role = "user";
|
|
50
|
+
const user_name = "user_name";
|
|
51
|
+
const exp = 1657026;
|
|
52
|
+
const payload = { exp };
|
|
53
|
+
const token = "token";
|
|
54
|
+
const data = { token };
|
|
55
|
+
expect(() => {
|
|
56
|
+
testSaga(refreshSaga, { payload })
|
|
57
|
+
.next()
|
|
58
|
+
.call(refreshDelay, payload)
|
|
59
|
+
.next(5000)
|
|
60
|
+
.delay(5000)
|
|
61
|
+
.next()
|
|
62
|
+
.put(refreshSession.request(payload))
|
|
63
|
+
.next()
|
|
64
|
+
.call(apiJsonPost, API_SESSIONS_REFRESH, {}, JSON_OPTS)
|
|
65
|
+
.next({ data })
|
|
66
|
+
.call(saveToken, token)
|
|
67
|
+
.next()
|
|
68
|
+
.call(jwt_decode, token)
|
|
69
|
+
.next({ amr, entitlements, exp, groups, role, user_name })
|
|
70
|
+
.put(
|
|
71
|
+
refreshSession.success({
|
|
72
|
+
amr,
|
|
73
|
+
entitlements,
|
|
74
|
+
exp,
|
|
75
|
+
groups,
|
|
76
|
+
role,
|
|
77
|
+
user_name,
|
|
78
|
+
token,
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
.next()
|
|
82
|
+
.put(refreshSession.fulfill())
|
|
83
|
+
.next()
|
|
84
|
+
.isDone();
|
|
85
|
+
}).not.toThrow();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
1
2
|
import jwt_decode from "jwt-decode";
|
|
2
3
|
import { testSaga } from "redux-saga-test-plan";
|
|
3
4
|
import { readToken as readTokenFromStorage } from "@truedat/core/services/storage";
|
|
4
5
|
import { tokenRequestSaga, checkExpired, readToken } from "../token";
|
|
5
|
-
import {
|
|
6
|
+
import { retrieveToken } from "../../routines";
|
|
6
7
|
|
|
7
8
|
describe("sagas: check expired", () => {
|
|
8
|
-
const now = Date.now().valueOf() / 1000;
|
|
9
|
+
const now = _.toInteger(Date.now().valueOf() / 1000);
|
|
9
10
|
const oneMinuteAgo = now - 60;
|
|
10
11
|
const oneMinuteFromNow = now + 60;
|
|
11
12
|
|
|
@@ -27,13 +28,13 @@ describe("sagas: check expired", () => {
|
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
describe("sagas: read token", () => {
|
|
30
|
-
const exp = Date.now().valueOf() / 1000 + 60;
|
|
31
|
+
const exp = _.toInteger(Date.now().valueOf() / 1000 + 60);
|
|
31
32
|
const user_name = "user";
|
|
32
33
|
const token = "TOKEN";
|
|
33
34
|
const role = "user";
|
|
34
|
-
const
|
|
35
|
-
const access_method = undefined;
|
|
35
|
+
const amr = undefined;
|
|
36
36
|
const groups = ["business_glossary"];
|
|
37
|
+
const entitlements = ["p"];
|
|
37
38
|
|
|
38
39
|
it("should call readTokenFromStorage, jwt_decode and checkExpired", () => {
|
|
39
40
|
expect(() => {
|
|
@@ -42,18 +43,20 @@ describe("sagas: read token", () => {
|
|
|
42
43
|
.call(readTokenFromStorage)
|
|
43
44
|
.next(token)
|
|
44
45
|
.call(jwt_decode, token)
|
|
45
|
-
.next({ user_name, exp, role,
|
|
46
|
+
.next({ user_name, entitlements, exp, role, groups })
|
|
46
47
|
.call(checkExpired, exp)
|
|
47
48
|
.next(false)
|
|
48
|
-
.put(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
.put(
|
|
50
|
+
retrieveToken.success({
|
|
51
|
+
amr,
|
|
52
|
+
entitlements,
|
|
53
|
+
exp,
|
|
54
|
+
groups,
|
|
55
|
+
role,
|
|
56
|
+
token,
|
|
57
|
+
user_name,
|
|
58
|
+
})
|
|
59
|
+
)
|
|
57
60
|
.next()
|
|
58
61
|
.isDone();
|
|
59
62
|
}).not.toThrow();
|
|
@@ -89,7 +92,7 @@ describe("sagas: token request", () => {
|
|
|
89
92
|
expect(() => {
|
|
90
93
|
testSaga(tokenRequestSaga)
|
|
91
94
|
.next()
|
|
92
|
-
.takeLatest(
|
|
95
|
+
.takeLatest(retrieveToken.TRIGGER, readToken)
|
|
93
96
|
.finish()
|
|
94
97
|
.isDone();
|
|
95
98
|
}).not.toThrow();
|
|
@@ -2,17 +2,20 @@ import { fetchAuthMethodsRequestSaga } from "./fetchAuthMethods";
|
|
|
2
2
|
import { loginRequestSaga } from "./login";
|
|
3
3
|
import { logoutRequestSaga } from "./logout";
|
|
4
4
|
import { tokenRequestSaga } from "./token";
|
|
5
|
+
import { refreshRequestSaga } from "./refresh";
|
|
5
6
|
|
|
6
7
|
export {
|
|
7
8
|
fetchAuthMethodsRequestSaga,
|
|
8
9
|
loginRequestSaga,
|
|
9
10
|
logoutRequestSaga,
|
|
10
|
-
tokenRequestSaga
|
|
11
|
+
tokenRequestSaga,
|
|
12
|
+
refreshRequestSaga,
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export default [
|
|
14
16
|
fetchAuthMethodsRequestSaga(),
|
|
15
17
|
loginRequestSaga(),
|
|
16
18
|
logoutRequestSaga(),
|
|
17
|
-
tokenRequestSaga()
|
|
19
|
+
tokenRequestSaga(),
|
|
20
|
+
refreshRequestSaga(),
|
|
18
21
|
];
|
|
@@ -23,18 +23,18 @@ export function* auth0LoginSaga({ payload }) {
|
|
|
23
23
|
const { data } = yield call(apiJsonPost, url, body, opts);
|
|
24
24
|
const { token } = data;
|
|
25
25
|
yield call(saveToken, token);
|
|
26
|
-
const {
|
|
26
|
+
const { amr, entitlements, exp, groups, role, user_name } = yield call(
|
|
27
27
|
jwt_decode,
|
|
28
28
|
token
|
|
29
29
|
);
|
|
30
30
|
yield put(
|
|
31
31
|
auth0Login.success({
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
amr,
|
|
33
|
+
entitlements,
|
|
34
34
|
exp,
|
|
35
|
-
has_permissions,
|
|
36
35
|
groups,
|
|
37
36
|
role,
|
|
37
|
+
user_name,
|
|
38
38
|
...data,
|
|
39
39
|
})
|
|
40
40
|
);
|
|
@@ -58,19 +58,18 @@ export function* openIdLoginSaga({ payload }) {
|
|
|
58
58
|
const { data } = yield call(apiJsonPost, url, body, opts);
|
|
59
59
|
const { token } = data;
|
|
60
60
|
yield call(saveToken, token);
|
|
61
|
-
const {
|
|
61
|
+
const { amr, entitlements, exp, groups, role, user_name } = yield call(
|
|
62
62
|
jwt_decode,
|
|
63
63
|
token
|
|
64
64
|
);
|
|
65
|
-
// TODO: push username to GTM dataLayer
|
|
66
65
|
yield put(
|
|
67
66
|
openIdLogin.success({
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
amr,
|
|
68
|
+
entitlements,
|
|
70
69
|
exp,
|
|
71
|
-
has_permissions,
|
|
72
|
-
role,
|
|
73
70
|
groups,
|
|
71
|
+
role,
|
|
72
|
+
user_name,
|
|
74
73
|
...data,
|
|
75
74
|
})
|
|
76
75
|
);
|
|
@@ -90,18 +89,18 @@ export function* nonceLoginSaga({ payload }) {
|
|
|
90
89
|
const { data } = yield call(apiJsonPost, url, body, JSON_OPTS);
|
|
91
90
|
const { token } = data;
|
|
92
91
|
yield call(saveToken, token);
|
|
93
|
-
const {
|
|
92
|
+
const { amr, entitlements, exp, groups, role, user_name } = yield call(
|
|
94
93
|
jwt_decode,
|
|
95
94
|
token
|
|
96
95
|
);
|
|
97
96
|
yield put(
|
|
98
97
|
nonceLogin.success({
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
amr,
|
|
99
|
+
entitlements,
|
|
101
100
|
exp,
|
|
102
|
-
has_permissions,
|
|
103
|
-
role,
|
|
104
101
|
groups,
|
|
102
|
+
role,
|
|
103
|
+
user_name,
|
|
105
104
|
...data,
|
|
106
105
|
})
|
|
107
106
|
);
|
|
@@ -118,30 +117,24 @@ export function* postLoginSaga({ payload }) {
|
|
|
118
117
|
const auth_realm = _.prop("auth_realm")(payload);
|
|
119
118
|
const user = _.pick(["user_name", "password"])(payload);
|
|
120
119
|
const requestData = auth_realm
|
|
121
|
-
? { access_method: "
|
|
122
|
-
: { access_method: "
|
|
120
|
+
? { access_method: "pwd", auth_realm, user }
|
|
121
|
+
: { access_method: "pwd", user };
|
|
123
122
|
yield put(login.request());
|
|
124
123
|
const { data } = yield call(apiJsonPost, url, requestData, JSON_OPTS);
|
|
125
124
|
const { token } = data;
|
|
126
125
|
yield call(saveToken, token);
|
|
127
|
-
const {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
exp,
|
|
132
|
-
role,
|
|
133
|
-
groups,
|
|
134
|
-
access_method,
|
|
135
|
-
} = yield call(jwt_decode, token);
|
|
126
|
+
const { amr, entitlements, exp, groups, role, user_name } = yield call(
|
|
127
|
+
jwt_decode,
|
|
128
|
+
token
|
|
129
|
+
);
|
|
136
130
|
yield put(
|
|
137
131
|
login.success({
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
type,
|
|
132
|
+
amr,
|
|
133
|
+
entitlements,
|
|
141
134
|
exp,
|
|
142
|
-
role,
|
|
143
135
|
groups,
|
|
144
|
-
|
|
136
|
+
role,
|
|
137
|
+
user_name,
|
|
145
138
|
...data,
|
|
146
139
|
})
|
|
147
140
|
);
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import { call, put, takeLatest } from "redux-saga/effects";
|
|
1
|
+
import { all, call, put, takeLatest } from "redux-saga/effects";
|
|
2
|
+
import { apiJsonDelete, JSON_OPTS } from "@truedat/core/services/api";
|
|
2
3
|
import { clearToken } from "@truedat/core/services/storage";
|
|
3
|
-
import {
|
|
4
|
+
import { API_SESSIONS } from "../api";
|
|
5
|
+
import { logout } from "../routines";
|
|
4
6
|
|
|
5
7
|
export function* logoutSaga() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
try {
|
|
9
|
+
yield put(logout.request());
|
|
10
|
+
const { data } = yield call(apiJsonDelete, API_SESSIONS, JSON_OPTS);
|
|
11
|
+
yield put(logout.success(data));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error.response) {
|
|
14
|
+
const { status, data } = error.response;
|
|
15
|
+
yield put(logout.failure({ status, data }));
|
|
16
|
+
} else {
|
|
17
|
+
yield put(logout.failure(error.message));
|
|
18
|
+
}
|
|
19
|
+
} finally {
|
|
20
|
+
yield call(clearToken);
|
|
21
|
+
yield put(logout.fulfill());
|
|
22
|
+
}
|
|
8
23
|
}
|
|
9
24
|
|
|
10
25
|
export function* logoutRequestSaga() {
|
|
11
|
-
yield takeLatest(
|
|
26
|
+
yield takeLatest(logout.TRIGGER, logoutSaga);
|
|
12
27
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import jwt_decode from "jwt-decode";
|
|
2
|
+
import { all, call, put, takeLatest, delay } from "redux-saga/effects";
|
|
3
|
+
import { apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
|
|
4
|
+
import { saveToken } from "@truedat/core/services/storage";
|
|
5
|
+
import {
|
|
6
|
+
login,
|
|
7
|
+
auth0Login,
|
|
8
|
+
openIdLogin,
|
|
9
|
+
nonceLogin,
|
|
10
|
+
refreshSession,
|
|
11
|
+
retrieveToken,
|
|
12
|
+
} from "../routines";
|
|
13
|
+
import { API_SESSIONS_REFRESH } from "../api";
|
|
14
|
+
|
|
15
|
+
// 5 seconds
|
|
16
|
+
const MIN_DELAY = 5000;
|
|
17
|
+
|
|
18
|
+
// milliseconds until 1 minute before expiry, but at least MIN_DELAY
|
|
19
|
+
export const refreshDelay = ({ exp }) =>
|
|
20
|
+
exp ? Math.max(MIN_DELAY, exp * 1000 - Date.now() - 60000) : MIN_DELAY;
|
|
21
|
+
|
|
22
|
+
export function* refreshSaga({ payload }) {
|
|
23
|
+
const millis = yield call(refreshDelay, payload || {});
|
|
24
|
+
yield delay(millis);
|
|
25
|
+
yield put(refreshSession.request(payload));
|
|
26
|
+
try {
|
|
27
|
+
const { data } = yield call(
|
|
28
|
+
apiJsonPost,
|
|
29
|
+
API_SESSIONS_REFRESH,
|
|
30
|
+
{},
|
|
31
|
+
JSON_OPTS
|
|
32
|
+
);
|
|
33
|
+
const { token } = data;
|
|
34
|
+
yield call(saveToken, token);
|
|
35
|
+
const { amr, entitlements, exp, groups, role, user_name } = yield call(
|
|
36
|
+
jwt_decode,
|
|
37
|
+
token
|
|
38
|
+
);
|
|
39
|
+
yield put(
|
|
40
|
+
refreshSession.success({
|
|
41
|
+
amr,
|
|
42
|
+
entitlements,
|
|
43
|
+
exp,
|
|
44
|
+
groups,
|
|
45
|
+
role,
|
|
46
|
+
user_name,
|
|
47
|
+
...data,
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
yield put(refreshSession.failure(error.message));
|
|
52
|
+
} finally {
|
|
53
|
+
yield put(refreshSession.fulfill());
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function* refreshRequestSaga() {
|
|
58
|
+
yield all([
|
|
59
|
+
takeLatest(login.SUCCESS, refreshSaga),
|
|
60
|
+
takeLatest(auth0Login.SUCCESS, refreshSaga),
|
|
61
|
+
takeLatest(openIdLogin.SUCCESS, refreshSaga),
|
|
62
|
+
takeLatest(nonceLogin.SUCCESS, refreshSaga),
|
|
63
|
+
takeLatest(refreshSession.SUCCESS, refreshSaga),
|
|
64
|
+
takeLatest(retrieveToken.SUCCESS, refreshSaga),
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import jwt_decode from "jwt-decode";
|
|
2
2
|
import { call, put, takeLatest } from "redux-saga/effects";
|
|
3
3
|
import { readToken as readTokenFromStorage } from "@truedat/core/services/storage";
|
|
4
|
-
import {
|
|
4
|
+
import { retrieveToken } from "../routines";
|
|
5
5
|
|
|
6
6
|
export const checkExpired = (exp) => {
|
|
7
7
|
if (exp) {
|
|
@@ -15,23 +15,27 @@ export const checkExpired = (exp) => {
|
|
|
15
15
|
export function* readToken() {
|
|
16
16
|
const token = yield call(readTokenFromStorage);
|
|
17
17
|
if (token) {
|
|
18
|
-
const {
|
|
19
|
-
|
|
18
|
+
const { amr, entitlements, exp, groups, role, user_name } = yield call(
|
|
19
|
+
jwt_decode,
|
|
20
|
+
token
|
|
21
|
+
);
|
|
20
22
|
const isExpired = yield call(checkExpired, exp);
|
|
21
23
|
if (!isExpired) {
|
|
22
|
-
yield put(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
yield put(
|
|
25
|
+
retrieveToken.success({
|
|
26
|
+
amr,
|
|
27
|
+
entitlements,
|
|
28
|
+
exp,
|
|
29
|
+
groups,
|
|
30
|
+
role,
|
|
31
|
+
token,
|
|
32
|
+
user_name,
|
|
33
|
+
})
|
|
34
|
+
);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export function* tokenRequestSaga() {
|
|
36
|
-
yield takeLatest(
|
|
40
|
+
yield takeLatest(retrieveToken.TRIGGER, readToken);
|
|
37
41
|
}
|
|
@@ -17,9 +17,7 @@ describe("sagas: fetchUsersRequestSaga", () => {
|
|
|
17
17
|
|
|
18
18
|
it("should throw exception if an unhandled action is received", () => {
|
|
19
19
|
expect(() => {
|
|
20
|
-
testSaga(fetchUsersRequestSaga)
|
|
21
|
-
.next()
|
|
22
|
-
.takeLatest("FOO", fetchUsersSaga);
|
|
20
|
+
testSaga(fetchUsersRequestSaga).next().takeLatest("FOO", fetchUsersSaga);
|
|
23
21
|
}).toThrow();
|
|
24
22
|
});
|
|
25
23
|
});
|
|
@@ -28,8 +26,8 @@ describe("sagas: fetchUsersSaga", () => {
|
|
|
28
26
|
const data = {
|
|
29
27
|
collection: [
|
|
30
28
|
{ id: 1, name: "User 1", email: "user1@truedat.net" },
|
|
31
|
-
{ id: 2, name: "User 2", email: "user2@truedat.net" }
|
|
32
|
-
]
|
|
29
|
+
{ id: 2, name: "User 2", email: "user2@truedat.net" },
|
|
30
|
+
],
|
|
33
31
|
};
|
|
34
32
|
|
|
35
33
|
it("should put a success action when a response is returned", () => {
|