@edx/frontend-platform 4.6.0 → 4.6.2
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/.env.development +30 -0
- package/.env.test +30 -0
- package/.eslintignore +6 -0
- package/.eslintrc.js +28 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/.github/workflows/add-depr-ticket-to-depr-board.yml +19 -0
- package/.github/workflows/add-remove-label-on-comment.yml +20 -0
- package/.github/workflows/ci.yml +42 -0
- package/.github/workflows/commitlint.yml +10 -0
- package/.github/workflows/lockfileversion-check.yml +13 -0
- package/.github/workflows/manual-publish.yml +43 -0
- package/.github/workflows/npm-deprecate.yml +22 -0
- package/.github/workflows/release.yml +45 -0
- package/.github/workflows/self-assign-issue.yml +12 -0
- package/.github/workflows/update-browserslist-db.yml +12 -0
- package/.nvmrc +1 -0
- package/.releaserc +32 -0
- package/catalog-info.yaml +21 -0
- package/dist/LICENSE +661 -0
- package/dist/README.md +155 -0
- package/dist/package.json +86 -0
- package/docs/addTagsPlugin.js +10 -0
- package/docs/auth-API.md +114 -0
- package/docs/decisions/0001-record-architecture-decisions.rst +32 -0
- package/docs/decisions/0002-frontend-base-design-goals.rst +222 -0
- package/docs/decisions/0003-consolidation-into-frontend-platform.rst +71 -0
- package/docs/decisions/0004-axios-caching-implementation.rst +88 -0
- package/docs/decisions/0005-token-null-after-successful-refresh.rst +69 -0
- package/docs/decisions/0006-middleware-support-for-http-clients.rst +44 -0
- package/docs/decisions/0007-javascript-file-configuration.rst +143 -0
- package/docs/how_tos/automatic-case-conversion.rst +58 -0
- package/docs/how_tos/caching.rst +93 -0
- package/docs/how_tos/i18n.rst +305 -0
- package/docs/removeExport.js +24 -0
- package/docs/template/edx/README.md +12 -0
- package/docs/template/edx/publish.js +713 -0
- package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/template/edx/static/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/template/edx/static/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/docs/template/edx/static/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/template/edx/static/scripts/linenumber.js +25 -0
- package/docs/template/edx/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/template/edx/static/scripts/prettify/lang-css.js +2 -0
- package/docs/template/edx/static/scripts/prettify/prettify.js +28 -0
- package/docs/template/edx/static/styles/jsdoc-default.css +356 -0
- package/docs/template/edx/static/styles/prettify-jsdoc.css +111 -0
- package/docs/template/edx/static/styles/prettify-tomorrow.css +132 -0
- package/docs/template/edx/tmpl/augments.tmpl +10 -0
- package/docs/template/edx/tmpl/container.tmpl +196 -0
- package/docs/template/edx/tmpl/details.tmpl +143 -0
- package/docs/template/edx/tmpl/example.tmpl +2 -0
- package/docs/template/edx/tmpl/examples.tmpl +13 -0
- package/docs/template/edx/tmpl/exceptions.tmpl +32 -0
- package/docs/template/edx/tmpl/layout.tmpl +39 -0
- package/docs/template/edx/tmpl/mainpage.tmpl +10 -0
- package/docs/template/edx/tmpl/members.tmpl +38 -0
- package/docs/template/edx/tmpl/method.tmpl +131 -0
- package/docs/template/edx/tmpl/modifies.tmpl +14 -0
- package/docs/template/edx/tmpl/params.tmpl +131 -0
- package/docs/template/edx/tmpl/properties.tmpl +108 -0
- package/docs/template/edx/tmpl/returns.tmpl +19 -0
- package/docs/template/edx/tmpl/source.tmpl +8 -0
- package/docs/template/edx/tmpl/tutorial.tmpl +19 -0
- package/docs/template/edx/tmpl/type.tmpl +7 -0
- package/env.config.js +8 -0
- package/jsdoc.json +36 -0
- package/openedx.yaml +12 -0
- package/package.json +6 -6
- package/service-interface.png +0 -0
- package/src/analytics/MockAnalyticsService.js +71 -0
- package/src/analytics/SegmentAnalyticsService.js +243 -0
- package/src/analytics/index.js +12 -0
- package/src/analytics/interface.js +142 -0
- package/src/auth/AxiosCsrfTokenService.js +60 -0
- package/src/auth/AxiosJwtAuthService.js +364 -0
- package/src/auth/AxiosJwtTokenService.js +134 -0
- package/src/auth/LocalForageCache.js +78 -0
- package/src/auth/MockAuthService.js +285 -0
- package/src/auth/index.js +19 -0
- package/src/auth/interceptors/createCsrfTokenProviderInterceptor.js +37 -0
- package/src/auth/interceptors/createJwtTokenProviderInterceptor.js +38 -0
- package/src/auth/interceptors/createProcessAxiosRequestErrorInterceptor.js +20 -0
- package/src/auth/interceptors/createRetryInterceptor.js +72 -0
- package/src/auth/interface.js +309 -0
- package/src/auth/utils.js +105 -0
- package/src/config.js +327 -0
- package/src/constants.js +66 -0
- package/src/i18n/countries.js +57 -0
- package/src/i18n/index.js +123 -0
- package/src/i18n/injectIntlWithShim.jsx +45 -0
- package/src/i18n/languages.js +60 -0
- package/src/i18n/lib.js +282 -0
- package/src/i18n/scripts/README.md +29 -0
- package/src/i18n/scripts/intl-imports.js +259 -0
- package/src/i18n/scripts/transifex-utils.js +75 -0
- package/src/index.js +42 -0
- package/src/initialize.js +357 -0
- package/src/logging/MockLoggingService.js +31 -0
- package/src/logging/NewRelicLoggingService.js +181 -0
- package/src/logging/index.js +9 -0
- package/src/logging/interface.js +110 -0
- package/src/pubSub.js +47 -0
- package/src/react/AppContext.jsx +24 -0
- package/src/react/AppProvider.jsx +93 -0
- package/src/react/AuthenticatedPageRoute.jsx +60 -0
- package/src/react/ErrorBoundary.jsx +44 -0
- package/src/react/ErrorPage.jsx +76 -0
- package/src/react/LoginRedirect.jsx +16 -0
- package/src/react/OptionalReduxProvider.jsx +28 -0
- package/src/react/PageRoute.jsx +31 -0
- package/src/react/hooks.js +50 -0
- package/src/react/index.js +16 -0
- package/src/scripts/GoogleAnalyticsLoader.js +53 -0
- package/src/scripts/index.js +2 -0
- package/src/testing/index.js +9 -0
- package/src/testing/initializeMockApp.js +77 -0
- package/src/testing/mockMessages.js +21 -0
- package/src/utils.js +167 -0
- /package/{analytics → dist/analytics}/MockAnalyticsService.js +0 -0
- /package/{analytics → dist/analytics}/MockAnalyticsService.js.map +0 -0
- /package/{analytics → dist/analytics}/SegmentAnalyticsService.js +0 -0
- /package/{analytics → dist/analytics}/SegmentAnalyticsService.js.map +0 -0
- /package/{analytics → dist/analytics}/index.js +0 -0
- /package/{analytics → dist/analytics}/index.js.map +0 -0
- /package/{analytics → dist/analytics}/interface.js +0 -0
- /package/{analytics → dist/analytics}/interface.js.map +0 -0
- /package/{auth → dist/auth}/AxiosCsrfTokenService.js +0 -0
- /package/{auth → dist/auth}/AxiosCsrfTokenService.js.map +0 -0
- /package/{auth → dist/auth}/AxiosJwtAuthService.js +0 -0
- /package/{auth → dist/auth}/AxiosJwtAuthService.js.map +0 -0
- /package/{auth → dist/auth}/AxiosJwtTokenService.js +0 -0
- /package/{auth → dist/auth}/AxiosJwtTokenService.js.map +0 -0
- /package/{auth → dist/auth}/LocalForageCache.js +0 -0
- /package/{auth → dist/auth}/LocalForageCache.js.map +0 -0
- /package/{auth → dist/auth}/MockAuthService.js +0 -0
- /package/{auth → dist/auth}/MockAuthService.js.map +0 -0
- /package/{auth → dist/auth}/index.js +0 -0
- /package/{auth → dist/auth}/index.js.map +0 -0
- /package/{auth → dist/auth}/interceptors/createCsrfTokenProviderInterceptor.js +0 -0
- /package/{auth → dist/auth}/interceptors/createCsrfTokenProviderInterceptor.js.map +0 -0
- /package/{auth → dist/auth}/interceptors/createJwtTokenProviderInterceptor.js +0 -0
- /package/{auth → dist/auth}/interceptors/createJwtTokenProviderInterceptor.js.map +0 -0
- /package/{auth → dist/auth}/interceptors/createProcessAxiosRequestErrorInterceptor.js +0 -0
- /package/{auth → dist/auth}/interceptors/createProcessAxiosRequestErrorInterceptor.js.map +0 -0
- /package/{auth → dist/auth}/interceptors/createRetryInterceptor.js +0 -0
- /package/{auth → dist/auth}/interceptors/createRetryInterceptor.js.map +0 -0
- /package/{auth → dist/auth}/interface.js +0 -0
- /package/{auth → dist/auth}/interface.js.map +0 -0
- /package/{auth → dist/auth}/utils.js +0 -0
- /package/{auth → dist/auth}/utils.js.map +0 -0
- /package/{config.js → dist/config.js} +0 -0
- /package/{config.js.map → dist/config.js.map} +0 -0
- /package/{constants.js → dist/constants.js} +0 -0
- /package/{constants.js.map → dist/constants.js.map} +0 -0
- /package/{i18n → dist/i18n}/countries.js +0 -0
- /package/{i18n → dist/i18n}/countries.js.map +0 -0
- /package/{i18n → dist/i18n}/index.js +0 -0
- /package/{i18n → dist/i18n}/index.js.map +0 -0
- /package/{i18n → dist/i18n}/injectIntlWithShim.js +0 -0
- /package/{i18n → dist/i18n}/injectIntlWithShim.js.map +0 -0
- /package/{i18n → dist/i18n}/languages.js +0 -0
- /package/{i18n → dist/i18n}/languages.js.map +0 -0
- /package/{i18n → dist/i18n}/lib.js +0 -0
- /package/{i18n → dist/i18n}/lib.js.map +0 -0
- /package/{i18n → dist/i18n}/scripts/README.md +0 -0
- /package/{i18n → dist/i18n}/scripts/intl-imports.js +0 -0
- /package/{i18n → dist/i18n}/scripts/intl-imports.js.map +0 -0
- /package/{i18n → dist/i18n}/scripts/transifex-utils.js +0 -0
- /package/{i18n → dist/i18n}/scripts/transifex-utils.js.map +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{index.js.map → dist/index.js.map} +0 -0
- /package/{initialize.js → dist/initialize.js} +0 -0
- /package/{initialize.js.map → dist/initialize.js.map} +0 -0
- /package/{logging → dist/logging}/MockLoggingService.js +0 -0
- /package/{logging → dist/logging}/MockLoggingService.js.map +0 -0
- /package/{logging → dist/logging}/NewRelicLoggingService.js +0 -0
- /package/{logging → dist/logging}/NewRelicLoggingService.js.map +0 -0
- /package/{logging → dist/logging}/index.js +0 -0
- /package/{logging → dist/logging}/index.js.map +0 -0
- /package/{logging → dist/logging}/interface.js +0 -0
- /package/{logging → dist/logging}/interface.js.map +0 -0
- /package/{pubSub.js → dist/pubSub.js} +0 -0
- /package/{pubSub.js.map → dist/pubSub.js.map} +0 -0
- /package/{react → dist/react}/AppContext.js +0 -0
- /package/{react → dist/react}/AppContext.js.map +0 -0
- /package/{react → dist/react}/AppProvider.js +0 -0
- /package/{react → dist/react}/AppProvider.js.map +0 -0
- /package/{react → dist/react}/AuthenticatedPageRoute.js +0 -0
- /package/{react → dist/react}/AuthenticatedPageRoute.js.map +0 -0
- /package/{react → dist/react}/ErrorBoundary.js +0 -0
- /package/{react → dist/react}/ErrorBoundary.js.map +0 -0
- /package/{react → dist/react}/ErrorPage.js +0 -0
- /package/{react → dist/react}/ErrorPage.js.map +0 -0
- /package/{react → dist/react}/LoginRedirect.js +0 -0
- /package/{react → dist/react}/LoginRedirect.js.map +0 -0
- /package/{react → dist/react}/OptionalReduxProvider.js +0 -0
- /package/{react → dist/react}/OptionalReduxProvider.js.map +0 -0
- /package/{react → dist/react}/PageRoute.js +0 -0
- /package/{react → dist/react}/PageRoute.js.map +0 -0
- /package/{react → dist/react}/hooks.js +0 -0
- /package/{react → dist/react}/hooks.js.map +0 -0
- /package/{react → dist/react}/index.js +0 -0
- /package/{react → dist/react}/index.js.map +0 -0
- /package/{scripts → dist/scripts}/GoogleAnalyticsLoader.js +0 -0
- /package/{scripts → dist/scripts}/GoogleAnalyticsLoader.js.map +0 -0
- /package/{scripts → dist/scripts}/index.js +0 -0
- /package/{scripts → dist/scripts}/index.js.map +0 -0
- /package/{testing → dist/testing}/index.js +0 -0
- /package/{testing → dist/testing}/index.js.map +0 -0
- /package/{testing → dist/testing}/initializeMockApp.js +0 -0
- /package/{testing → dist/testing}/initializeMockApp.js.map +0 -0
- /package/{testing → dist/testing}/mockMessages.js +0 -0
- /package/{testing → dist/testing}/mockMessages.js.map +0 -0
- /package/{utils.js → dist/utils.js} +0 -0
- /package/{utils.js.map → dist/utils.js.map} +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #### Import members from **@edx/frontend-platform/auth**
|
|
3
|
+
*
|
|
4
|
+
* Simplifies the process of making authenticated API requests to backend edX services by providing
|
|
5
|
+
* common authN/authZ client code that enables the login/logout flow and handles ensuring the
|
|
6
|
+
* presence of a valid [JWT cookie](https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0009-jwt-in-session-cookie.rst).
|
|
7
|
+
*
|
|
8
|
+
* The `initialize` function performs much of the auth configuration for you. If, however, you're
|
|
9
|
+
* not using the `initialize` function, an authenticated API client can be created via:
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* import {
|
|
13
|
+
* configure,
|
|
14
|
+
* fetchAuthenticatedUser,
|
|
15
|
+
* getAuthenticatedHttpClient
|
|
16
|
+
* } from '@edx/frontend-platform/auth';
|
|
17
|
+
* import { getConfig } from '@edx/frontend-platform';
|
|
18
|
+
* import { getLoggingService } from '@edx/frontend-platform/logging';
|
|
19
|
+
*
|
|
20
|
+
* configure({
|
|
21
|
+
* loggingService: getLoggingService(),
|
|
22
|
+
* config: getConfig(),
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const authenticatedUser = await fetchAuthenticatedUser(); // validates and decodes JWT token
|
|
26
|
+
* const authenticatedHttpClient = getAuthenticatedHttpClient();
|
|
27
|
+
* const response = await getAuthenticatedHttpClient().get(`https://example.com/api/user/data/${authenticatedUser.username}`); // fetching from an authenticated API using user data
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* As shown in this example, auth depends on the configuration document and logging.
|
|
31
|
+
*
|
|
32
|
+
* NOTE: The documentation for AxiosJwtAuthService is nearly the same as that for the top-level
|
|
33
|
+
* auth interface, except that it contains some Axios-specific details.
|
|
34
|
+
*
|
|
35
|
+
* @module Auth
|
|
36
|
+
*/
|
|
37
|
+
import PropTypes from 'prop-types';
|
|
38
|
+
import { publish } from '../pubSub';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @constant
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
export const AUTHENTICATED_USER_TOPIC = 'AUTHENTICATED_USER';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Published when the authenticated user data changes. This can happen when the authentication
|
|
48
|
+
* service determines that the user is authenticated or anonymous, as well as when we fetch
|
|
49
|
+
* additional user account data if the `hydrateAuthenticatedUser` flag has been set in the
|
|
50
|
+
* `initialize` function.
|
|
51
|
+
*
|
|
52
|
+
* @event
|
|
53
|
+
* @see {@link module:Initialization~initialize}
|
|
54
|
+
*/
|
|
55
|
+
export const AUTHENTICATED_USER_CHANGED = `${AUTHENTICATED_USER_TOPIC}.CHANGED`;
|
|
56
|
+
|
|
57
|
+
const optionsShape = {
|
|
58
|
+
config: PropTypes.shape({
|
|
59
|
+
BASE_URL: PropTypes.string.isRequired,
|
|
60
|
+
LMS_BASE_URL: PropTypes.string.isRequired,
|
|
61
|
+
LOGIN_URL: PropTypes.string.isRequired,
|
|
62
|
+
LOGOUT_URL: PropTypes.string.isRequired,
|
|
63
|
+
REFRESH_ACCESS_TOKEN_ENDPOINT: PropTypes.string.isRequired,
|
|
64
|
+
ACCESS_TOKEN_COOKIE_NAME: PropTypes.string.isRequired,
|
|
65
|
+
CSRF_TOKEN_API_PATH: PropTypes.string.isRequired,
|
|
66
|
+
}).isRequired,
|
|
67
|
+
loggingService: PropTypes.shape({
|
|
68
|
+
logError: PropTypes.func.isRequired,
|
|
69
|
+
logInfo: PropTypes.func.isRequired,
|
|
70
|
+
}).isRequired,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const serviceShape = {
|
|
74
|
+
getAuthenticatedHttpClient: PropTypes.func.isRequired,
|
|
75
|
+
getHttpClient: PropTypes.func.isRequired,
|
|
76
|
+
getLoginRedirectUrl: PropTypes.func.isRequired,
|
|
77
|
+
redirectToLogin: PropTypes.func.isRequired,
|
|
78
|
+
getLogoutRedirectUrl: PropTypes.func.isRequired,
|
|
79
|
+
redirectToLogout: PropTypes.func.isRequired,
|
|
80
|
+
getAuthenticatedUser: PropTypes.func.isRequired,
|
|
81
|
+
setAuthenticatedUser: PropTypes.func.isRequired,
|
|
82
|
+
fetchAuthenticatedUser: PropTypes.func.isRequired,
|
|
83
|
+
ensureAuthenticatedUser: PropTypes.func.isRequired,
|
|
84
|
+
hydrateAuthenticatedUser: PropTypes.func.isRequired,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let service;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
*
|
|
91
|
+
* @param {class} AuthService
|
|
92
|
+
* @param {*} options
|
|
93
|
+
* @returns {AuthService}
|
|
94
|
+
*/
|
|
95
|
+
export function configure(AuthService, options) {
|
|
96
|
+
PropTypes.checkPropTypes(optionsShape, options, 'property', 'Auth');
|
|
97
|
+
service = new AuthService(options);
|
|
98
|
+
PropTypes.checkPropTypes(serviceShape, service, 'property', 'AuthService');
|
|
99
|
+
return service;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
*
|
|
104
|
+
*
|
|
105
|
+
* @returns {AuthService}
|
|
106
|
+
*/
|
|
107
|
+
export function getAuthService() {
|
|
108
|
+
if (!service) {
|
|
109
|
+
throw Error('You must first configure the auth service.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return service;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
export function resetAuthService() {
|
|
119
|
+
service = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Gets the authenticated HTTP client for the service.
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} [options] Optional options for how to configure the authenticated HTTP client
|
|
126
|
+
* @param {boolean} [options.useCache] Whether to use front end caching for all requests made with the returned client
|
|
127
|
+
*
|
|
128
|
+
* @returns {HttpClient}
|
|
129
|
+
*/
|
|
130
|
+
export function getAuthenticatedHttpClient(options = {}) {
|
|
131
|
+
return service.getAuthenticatedHttpClient(options);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Gets the unauthenticated HTTP client for the service.
|
|
136
|
+
*
|
|
137
|
+
* @param {Object} [options] Optional options for how to configure the authenticated HTTP client
|
|
138
|
+
* @param {boolean} [options.useCache] Whether to use front end caching for all requests made with the returned client
|
|
139
|
+
*
|
|
140
|
+
* @returns {HttpClient}
|
|
141
|
+
*/
|
|
142
|
+
export function getHttpClient(options = {}) {
|
|
143
|
+
return service.getHttpClient(options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Builds a URL to the login page with a post-login redirect URL attached as a query parameter.
|
|
148
|
+
*
|
|
149
|
+
* ```
|
|
150
|
+
* const url = getLoginRedirectUrl('http://localhost/mypage');
|
|
151
|
+
* console.log(url); // http://localhost/login?next=http%3A%2F%2Flocalhost%2Fmypage
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @param {string} redirectUrl The URL the user should be redirected to after logging in.
|
|
155
|
+
*/
|
|
156
|
+
export function getLoginRedirectUrl(redirectUrl) {
|
|
157
|
+
return service.getLoginRedirectUrl(redirectUrl);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Redirects the user to the login page.
|
|
162
|
+
*
|
|
163
|
+
* @param {string} redirectUrl The URL the user should be redirected to after logging in.
|
|
164
|
+
*/
|
|
165
|
+
export function redirectToLogin(redirectUrl) {
|
|
166
|
+
return service.redirectToLogin(redirectUrl);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Builds a URL to the logout page with a post-logout redirect URL attached as a query parameter.
|
|
171
|
+
*
|
|
172
|
+
* ```
|
|
173
|
+
* const url = getLogoutRedirectUrl('http://localhost/mypage');
|
|
174
|
+
* console.log(url); // http://localhost/logout?redirect_url=http%3A%2F%2Flocalhost%2Fmypage
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @param {string} redirectUrl The URL the user should be redirected to after logging out.
|
|
178
|
+
*/
|
|
179
|
+
export function getLogoutRedirectUrl(redirectUrl) {
|
|
180
|
+
return service.getLogoutRedirectUrl(redirectUrl);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Redirects the user to the logout page.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} redirectUrl The URL the user should be redirected to after logging out.
|
|
187
|
+
*/
|
|
188
|
+
export function redirectToLogout(redirectUrl) {
|
|
189
|
+
return service.redirectToLogout(redirectUrl);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* If it exists, returns the user data representing the currently authenticated user. If the
|
|
194
|
+
* user is anonymous, returns null.
|
|
195
|
+
*
|
|
196
|
+
* @returns {UserData|null}
|
|
197
|
+
*/
|
|
198
|
+
export function getAuthenticatedUser() {
|
|
199
|
+
return service.getAuthenticatedUser();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Sets the authenticated user to the provided value.
|
|
204
|
+
*
|
|
205
|
+
* @param {UserData} authUser
|
|
206
|
+
* @emits AUTHENTICATED_USER_CHANGED
|
|
207
|
+
*/
|
|
208
|
+
export function setAuthenticatedUser(authUser) {
|
|
209
|
+
service.setAuthenticatedUser(authUser);
|
|
210
|
+
publish(AUTHENTICATED_USER_CHANGED);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Reads the authenticated user's access token. Resolves to null if the user is
|
|
215
|
+
* unauthenticated.
|
|
216
|
+
*
|
|
217
|
+
* @returns {Promise<UserData>|Promise<null>} Resolves to the user's access token if they are
|
|
218
|
+
* logged in.
|
|
219
|
+
*/
|
|
220
|
+
export async function fetchAuthenticatedUser(options = {}) {
|
|
221
|
+
return service.fetchAuthenticatedUser(options);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Ensures a user is authenticated. It will redirect to login when not
|
|
226
|
+
* authenticated.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} [redirectUrl=config.BASE_URL] to return user after login when not
|
|
229
|
+
* authenticated.
|
|
230
|
+
* @returns {Promise<UserData>}
|
|
231
|
+
*/
|
|
232
|
+
export async function ensureAuthenticatedUser(redirectUrl) {
|
|
233
|
+
return service.ensureAuthenticatedUser(redirectUrl);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Fetches additional user account information for the authenticated user and merges it into the
|
|
238
|
+
* existing authenticatedUser object, available via getAuthenticatedUser().
|
|
239
|
+
*
|
|
240
|
+
* ```
|
|
241
|
+
* console.log(authenticatedUser); // Will be sparse and only contain basic information.
|
|
242
|
+
* await hydrateAuthenticatedUser()
|
|
243
|
+
* const authenticatedUser = getAuthenticatedUser();
|
|
244
|
+
* console.log(authenticatedUser); // Will contain additional user information
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* @emits AUTHENTICATED_USER_CHANGED
|
|
248
|
+
*/
|
|
249
|
+
export async function hydrateAuthenticatedUser() {
|
|
250
|
+
await service.hydrateAuthenticatedUser();
|
|
251
|
+
publish(AUTHENTICATED_USER_CHANGED);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @name AuthService
|
|
256
|
+
* @interface
|
|
257
|
+
* @memberof module:Auth
|
|
258
|
+
* @property {function} getAuthenticatedHttpClient
|
|
259
|
+
* @property {function} getHttpClient
|
|
260
|
+
* @property {function} getLoginRedirectUrl
|
|
261
|
+
* @property {function} redirectToLogin
|
|
262
|
+
* @property {function} getLogoutRedirectUrl
|
|
263
|
+
* @property {function} redirectToLogout
|
|
264
|
+
* @property {function} getAuthenticatedUser
|
|
265
|
+
* @property {function} setAuthenticatedUser
|
|
266
|
+
* @property {function} fetchAuthenticatedUser
|
|
267
|
+
* @property {function} ensureAuthenticatedUser
|
|
268
|
+
* @property {function} hydrateAuthenticatedUser
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* A configured axios client. See axios docs for more
|
|
273
|
+
* info https://github.com/axios/axios. All the functions
|
|
274
|
+
* below accept isPublic and isCsrfExempt in the request
|
|
275
|
+
* config options. Setting these to true will prevent this
|
|
276
|
+
* client from attempting to refresh the jwt access token
|
|
277
|
+
* or a csrf token respectively.
|
|
278
|
+
*
|
|
279
|
+
* ```
|
|
280
|
+
* // A public endpoint (no jwt token refresh)
|
|
281
|
+
* apiClient.get('/path/to/endpoint', { isPublic: true });
|
|
282
|
+
* ```
|
|
283
|
+
*
|
|
284
|
+
* ```
|
|
285
|
+
* // A csrf exempt endpoint
|
|
286
|
+
* apiClient.post('/path/to/endpoint', { data }, { isCsrfExempt: true });
|
|
287
|
+
* ```
|
|
288
|
+
*
|
|
289
|
+
* @name HttpClient
|
|
290
|
+
* @interface
|
|
291
|
+
* @memberof module:Auth
|
|
292
|
+
* @property {function} get
|
|
293
|
+
* @property {function} head
|
|
294
|
+
* @property {function} options
|
|
295
|
+
* @property {function} delete (csrf protected)
|
|
296
|
+
* @property {function} post (csrf protected)
|
|
297
|
+
* @property {function} put (csrf protected)
|
|
298
|
+
* @property {function} patch (csrf protected)
|
|
299
|
+
*/
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @name UserData
|
|
303
|
+
* @interface
|
|
304
|
+
* @memberof module:Auth
|
|
305
|
+
* @property {string} userId
|
|
306
|
+
* @property {string} username
|
|
307
|
+
* @property {Array} roles
|
|
308
|
+
* @property {boolean} administrator
|
|
309
|
+
*/
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Lifted from here: https://regexr.com/3ok5o
|
|
2
|
+
const urlRegex = /([a-z]{1,2}tps?):\/\/((?:(?!(?:\/|#|\?|&)).)+)(?:(\/(?:(?:(?:(?!(?:#|\?|&)).)+\/))?))?(?:((?:(?!(?:\.|$|\?|#)).)+))?(?:(\.(?:(?!(?:\?|$|#)).)+))?(?:(\?(?:(?!(?:$|#)).)+))?(?:(#.+))?/;
|
|
3
|
+
const getUrlParts = (url) => {
|
|
4
|
+
const found = url.match(urlRegex);
|
|
5
|
+
try {
|
|
6
|
+
const [
|
|
7
|
+
fullUrl,
|
|
8
|
+
protocol,
|
|
9
|
+
domain,
|
|
10
|
+
path,
|
|
11
|
+
endFilename,
|
|
12
|
+
endFileExtension,
|
|
13
|
+
query,
|
|
14
|
+
hash,
|
|
15
|
+
] = found;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
fullUrl,
|
|
19
|
+
protocol,
|
|
20
|
+
domain,
|
|
21
|
+
path,
|
|
22
|
+
endFilename,
|
|
23
|
+
endFileExtension,
|
|
24
|
+
query,
|
|
25
|
+
hash,
|
|
26
|
+
};
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new Error(`Could not find url parts from ${url}.`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const logFrontendAuthError = (loggingService, error) => {
|
|
33
|
+
const prefixedMessageError = Object.create(error);
|
|
34
|
+
prefixedMessageError.message = `[frontend-auth] ${error.message}`;
|
|
35
|
+
loggingService.logError(prefixedMessageError, prefixedMessageError.customAttributes);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const processAxiosError = (axiosErrorObject) => {
|
|
39
|
+
const error = Object.create(axiosErrorObject);
|
|
40
|
+
const { request, response, config } = error;
|
|
41
|
+
|
|
42
|
+
if (!config) {
|
|
43
|
+
error.customAttributes = {
|
|
44
|
+
...error.customAttributes,
|
|
45
|
+
httpErrorType: 'unknown-api-request-error',
|
|
46
|
+
};
|
|
47
|
+
return error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
url: httpErrorRequestUrl,
|
|
52
|
+
method: httpErrorRequestMethod,
|
|
53
|
+
} = config;
|
|
54
|
+
/* istanbul ignore else: difficult to enter the request-only error case in a unit test */
|
|
55
|
+
if (response) {
|
|
56
|
+
const { status, data } = response;
|
|
57
|
+
const stringifiedData = JSON.stringify(data) || '(empty response)';
|
|
58
|
+
const responseIsHTML = stringifiedData.includes('<!DOCTYPE html>');
|
|
59
|
+
// Don't include data if it is just an HTML document, like a 500 error page.
|
|
60
|
+
/* istanbul ignore next */
|
|
61
|
+
const httpErrorResponseData = responseIsHTML ? '<Response is HTML>' : stringifiedData;
|
|
62
|
+
error.customAttributes = {
|
|
63
|
+
...error.customAttributes,
|
|
64
|
+
httpErrorType: 'api-response-error',
|
|
65
|
+
httpErrorStatus: status,
|
|
66
|
+
httpErrorResponseData,
|
|
67
|
+
httpErrorRequestUrl,
|
|
68
|
+
httpErrorRequestMethod,
|
|
69
|
+
};
|
|
70
|
+
error.message = `Axios Error (Response): ${status} - See custom attributes for details.`;
|
|
71
|
+
} else if (request) {
|
|
72
|
+
error.customAttributes = {
|
|
73
|
+
...error.customAttributes,
|
|
74
|
+
httpErrorType: 'api-request-error',
|
|
75
|
+
httpErrorMessage: error.message,
|
|
76
|
+
httpErrorRequestUrl,
|
|
77
|
+
httpErrorRequestMethod,
|
|
78
|
+
};
|
|
79
|
+
// This case occurs most likely because of intermittent internet connection issues
|
|
80
|
+
// but it also, though less often, catches CORS or server configuration problems.
|
|
81
|
+
error.message = 'Axios Error (Request): (Possible local connectivity issue.) See custom attributes for details.';
|
|
82
|
+
} else {
|
|
83
|
+
error.customAttributes = {
|
|
84
|
+
...error.customAttributes,
|
|
85
|
+
httpErrorType: 'api-request-config-error',
|
|
86
|
+
httpErrorMessage: error.message,
|
|
87
|
+
httpErrorRequestUrl,
|
|
88
|
+
httpErrorRequestMethod,
|
|
89
|
+
};
|
|
90
|
+
error.message = 'Axios Error (Config): See custom attributes for details.';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return error;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const processAxiosErrorAndThrow = (axiosErrorObject) => {
|
|
97
|
+
throw processAxiosError(axiosErrorObject);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
getUrlParts,
|
|
102
|
+
logFrontendAuthError,
|
|
103
|
+
processAxiosError,
|
|
104
|
+
processAxiosErrorAndThrow,
|
|
105
|
+
};
|