@cortezaproject/corteza-vue 2021.9.7 → 2021.9.8

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.
@@ -0,0 +1,5 @@
1
+ declare namespace _default {
2
+ export { itemPicker };
3
+ }
4
+ export default _default;
5
+ import * as itemPicker from "../itemPicker/C3";
@@ -1,4 +1,4 @@
1
- export declare function getDate(value: string): string | undefined;
2
- export declare function getTime(value: string): string | undefined;
3
- export declare function setDate(date: string, value: string, noDate?: boolean, noTime?: boolean): string | undefined;
4
- export declare function setTime(time: string, value: string, noDate?: boolean, noTime?: boolean): string | undefined;
1
+ export declare function getDate(value: string | undefined): string | undefined;
2
+ export declare function getTime(value: string | undefined): string | undefined;
3
+ export declare function setDate(date: string | undefined, value: string | undefined, noDate?: boolean, noTime?: boolean): string | undefined;
4
+ export declare function setTime(time: string | undefined, value: string | undefined, noDate?: boolean, noTime?: boolean): string | undefined;
@@ -0,0 +1 @@
1
+ export { default as CItemPicker } from "./CItemPicker.c3";
@@ -0,0 +1,32 @@
1
+ /// <reference path="../../src/vue.shims.d.ts" />
2
+ declare namespace _default {
3
+ export const name: string;
4
+ export const group: string[];
5
+ export { component };
6
+ export { props };
7
+ export const controls: any[];
8
+ }
9
+ export default _default;
10
+ import { default as component } from "*.vue";
11
+ declare namespace props {
12
+ export const options: {
13
+ value: string;
14
+ text: string;
15
+ }[];
16
+ export const value: string[];
17
+ export namespace labels {
18
+ export const searchPlaceholder: string;
19
+ export const availableItems: string;
20
+ export const selectAllItems: string;
21
+ export const selectedItems: string;
22
+ export const systemItem: string;
23
+ export const unselectAllItems: string;
24
+ export const noItemsFound: string;
25
+ }
26
+ export const hideFilter: boolean;
27
+ export const hideIcons: boolean;
28
+ export const disabled: boolean;
29
+ export const disabledFilter: boolean;
30
+ export const disabledSorting: boolean;
31
+ export const disabledDragging: boolean;
32
+ }
@@ -1,2 +1,2 @@
1
1
  export { default as CItemPicker } from './CItemPicker.vue';
2
- export { default as CItem } from './CItem.vue';
2
+ export { default as CItemPickerItem } from './CItemPickerItem.vue';
@@ -0,0 +1,4 @@
1
+ export { default as CReportChart } from './Viewers/CReportChart.vue';
2
+ export { default as CReportMetric } from './Viewers/CReportMetric.vue';
3
+ export { default as CReportTable } from './Viewers/CReportTable.vue';
4
+ export { default as CReportText } from './Viewers/CReportText.vue';
package/dist/index.es.js CHANGED
@@ -1920,15 +1920,19 @@ var url = /*#__PURE__*/Object.freeze({
1920
1920
  const accessToken = Symbol('accessToken');
1921
1921
  const user = Symbol('user');
1922
1922
  /**
1923
- * This is an endpoint of a oauth2 authorization-code flow + refresh token exchange
1923
+ * This is an endpoint of an oauth2 authorization-code flow + refresh token exchange
1924
1924
  * for default client.
1925
1925
  *
1926
- * If you are concerned about security, this is not much different than using a dedicated backend
1926
+ * If you are concerned about security, this is not much different to using a dedicated backend
1927
1927
  * for this SPA that only redirects to a set of allowed URIs.
1928
1928
  */
1929
1929
  const oauth2FlowURL = '/oauth2/default-client';
1930
1930
  const oauth2InfoURL = '/oauth2/info';
1931
1931
  const oauth2Scope = 'profile api';
1932
+ const storeKeyFlowStarted = 'auth.flow-started';
1933
+ const storeKeyFinalState = 'auth.state.final';
1934
+ const storeKeyRefreshToken = 'auth.refresh-token';
1935
+ const maxStartAttempts = 5;
1932
1936
  class Auth {
1933
1937
  constructor({ app, verbose, cortezaAuthURL, callbackURL, entrypointURL, location, sessionStorage, refreshFactor }) {
1934
1938
  if (refreshFactor >= 1 || refreshFactor <= 0) {
@@ -1971,13 +1975,6 @@ class Auth {
1971
1975
  error: noop,
1972
1976
  };
1973
1977
  }
1974
- /**
1975
- * Returns prefixed key
1976
- * @param key
1977
- */
1978
- get refreshTokenKey() {
1979
- return 'auth.refresh-token';
1980
- }
1981
1978
  /**
1982
1979
  * Returns function that returns current access token
1983
1980
  */
@@ -2001,43 +1998,83 @@ class Auth {
2001
1998
  handle(req = new URL(this.entrypointURL)) {
2002
1999
  return __awaiter(this, void 0, void 0, function* () {
2003
2000
  this.log.info('handling authentication');
2004
- if (/\/auth\/callback$/.test(req.pathname)) {
2001
+ if (this.isCallback(req.pathname)) {
2005
2002
  this.log.info('handling authentication callback');
2006
2003
  const params = new URLSearchParams(req.search);
2007
2004
  if (params.has('error')) {
2008
2005
  throw new Error(params.get('error') || 'authentication flow failed with error');
2009
2006
  }
2010
2007
  if (params.has('code')) {
2011
- let finalLocation = null;
2008
+ let finalLocation = this.entrypointURL;
2012
2009
  if (params.has('state')) {
2013
2010
  const state = params.get('state') || '';
2014
- const key = `auth.state.${state}.location`;
2015
- this.log.info('state received', state);
2016
- finalLocation = this.sessionStorage.getItem(key);
2017
- if (!finalLocation) {
2018
- throw Error('state does not match');
2011
+ const storeKeyStateLocation = `auth.state.${state}.location`;
2012
+ const tmp = this.sessionStorage.getItem(storeKeyStateLocation);
2013
+ if (tmp === null) {
2014
+ console.warn('state does not match, restarting authentication flow');
2015
+ this.startAuthenticationFlow();
2016
+ return null;
2019
2017
  }
2020
- if (/auth\/callback/.test(finalLocation)) {
2018
+ if (!this.isCallback(tmp)) {
2021
2019
  // if by some coincidence we got callback URL to finalLocation
2022
- // we'll silently ignore it
2023
- finalLocation = null;
2024
- }
2025
- else {
2026
- this.log.info('location before auth flow start', finalLocation);
2020
+ // we'll silently ignore it and redirect user back to entrypoint
2021
+ finalLocation = tmp;
2027
2022
  }
2028
- this.sessionStorage.removeItem(key);
2023
+ this.sessionStorage.removeItem(storeKeyStateLocation);
2029
2024
  }
2030
2025
  const code = params.get('code') || '';
2031
2026
  this.log.info('authorization code received', code);
2032
- yield this.exchangeCode(code);
2033
- if (finalLocation) {
2034
- this.location.assign(finalLocation);
2035
- }
2027
+ const rsp = yield this.exchangeCode(code);
2028
+ this.startFinalState(finalLocation);
2029
+ return rsp;
2036
2030
  }
2037
2031
  }
2038
2032
  return this.check();
2039
2033
  });
2040
2034
  }
2035
+ /**
2036
+ * Flagging session storage
2037
+ *
2038
+ * Challenge with sessions and browser tabs:
2039
+ * Each browser tab & window interacts with an isolated session. When user clicks on a link to and wants to open
2040
+ * it in a new window or a tab, or when tab is duplicated, session contents are copied!
2041
+ *
2042
+ * Consequences of that are that two (or more) tabs end up with the same session
2043
+ * and the same refresh token, and we need to detect if we're dealing with refresh token in an old or new session.
2044
+ *
2045
+ * With this function we start the final state and flag the session. This way we'll
2046
+ * know, after the redirection to the final location, if this is a final stage or not and if the refresh token
2047
+ * belongs to this session or not.
2048
+ */
2049
+ startFinalState(dst) {
2050
+ this.log.info('redirecting back to final destination', dst);
2051
+ this.sessionStorage.setItem(storeKeyFinalState, Date.now().toString());
2052
+ this.location.assign(dst);
2053
+ }
2054
+ /**
2055
+ * Called when refresh token is re-fetched.
2056
+ *
2057
+ * Cleanup aux items in the session store
2058
+ */
2059
+ completeFinalState() {
2060
+ this.sessionStorage.removeItem(storeKeyFinalState);
2061
+ this.sessionStorage.removeItem(storeKeyFlowStarted);
2062
+ const stateKey = /^auth\.state\.\w+\.location$/;
2063
+ for (let i = 0; i < this.sessionStorage.length; i++) {
2064
+ const key = this.sessionStorage.key(i);
2065
+ if (key !== null && stateKey.test(key)) {
2066
+ this.sessionStorage.removeItem(key);
2067
+ }
2068
+ }
2069
+ }
2070
+ /**
2071
+ * Are we in a final state or not?
2072
+ *
2073
+ * See startFinalState function for more details
2074
+ */
2075
+ inFinalState() {
2076
+ return this.sessionStorage.getItem(storeKeyFinalState) !== null;
2077
+ }
2041
2078
  /**
2042
2079
  * Checks current auth state; is access token loaded OR do we have a refresh token we can use
2043
2080
  *
@@ -2071,27 +2108,34 @@ class Auth {
2071
2108
  this[user] = authUser;
2072
2109
  return data;
2073
2110
  }).catch((error) => {
2074
- this.log.info('data fetch form info endpoint failed', { oauth2InfoURL, headers, error });
2111
+ this.log.error('data fetch form info endpoint failed', { oauth2InfoURL, headers, error });
2075
2112
  // assume invalid JWT and remove it
2076
2113
  this[accessToken] = undefined;
2077
2114
  throw new Error('Unauthenticated');
2078
2115
  });
2079
2116
  }
2080
- const refreshToken = this.sessionStorage.getItem(this.refreshTokenKey);
2117
+ const refreshToken = this.sessionStorage.getItem(storeKeyRefreshToken);
2081
2118
  if (refreshToken) {
2082
- this.log.info('refresh token found, exchanging it for new access token');
2119
+ this.log.debug('refresh token found', { refreshToken });
2083
2120
  /**
2084
- * Refresh token found in the storage,
2085
- * let's use it to get new access token
2121
+ * Only exchange refresh token if this is the final state (see startFinalState function for more details)
2122
+ *
2123
+ * If this is a duplicated-session, an error will be thrown and authentication will be (probably)
2124
+ * restarted by the caller.
2086
2125
  */
2087
- return this.exchangeRefresh(refreshToken);
2126
+ if (this.inFinalState()) {
2127
+ this.log.info('refreshing token', refreshToken);
2128
+ /**
2129
+ * Refresh token found in the storage,
2130
+ * let's use it to get new access token
2131
+ */
2132
+ return this.exchangeRefresh(refreshToken);
2133
+ }
2088
2134
  }
2089
2135
  throw new Error('Unauthenticated');
2090
2136
  });
2091
2137
  }
2092
2138
  logout() {
2093
- this[accessToken] = undefined;
2094
- this[user] = undefined;
2095
2139
  this.pruneStore();
2096
2140
  this.location.assign(Make({
2097
2141
  url: `${this.cortezaAuthURL}/logout`,
@@ -2106,8 +2150,9 @@ class Auth {
2106
2150
  */
2107
2151
  startAuthenticationFlow() {
2108
2152
  this.log.debug('starting new authentication flow');
2153
+ this.incFlowCounter();
2109
2154
  const state = Math.random().toString(36).substring(2);
2110
- this.sessionStorage.setItem(`auth.state.${state}.location`, this.location.toString());
2155
+ this.sessionStorage.setItem(`auth.state.${state}.location`, this.getRedirect(this.location.toString()));
2111
2156
  this.location.assign(Make({
2112
2157
  url: `${this.cortezaAuthURL}` + oauth2FlowURL,
2113
2158
  query: {
@@ -2118,6 +2163,38 @@ class Auth {
2118
2163
  },
2119
2164
  }));
2120
2165
  }
2166
+ getRedirect(url) {
2167
+ const u = new URL(url);
2168
+ // In case someone started the flow on a callback route, default to the root
2169
+ // of the webapp.
2170
+ if (this.isCallback(u.pathname)) {
2171
+ u.pathname = '';
2172
+ u.search = '';
2173
+ u.hash = '';
2174
+ }
2175
+ return u.toString();
2176
+ }
2177
+ isCallback(url) {
2178
+ return /\/auth\/callback$/.test(url);
2179
+ }
2180
+ /**
2181
+ * protects against too many tries when we try to auto-fix the "state does not match" error
2182
+ * by restarting the aut flow.
2183
+ */
2184
+ incFlowCounter() {
2185
+ const aux = this.sessionStorage.getItem(storeKeyFlowStarted);
2186
+ if (aux === null) {
2187
+ this.sessionStorage.setItem(storeKeyFlowStarted, '1');
2188
+ return;
2189
+ }
2190
+ const count = parseInt(this.sessionStorage.getItem(storeKeyFlowStarted));
2191
+ if (count >= maxStartAttempts) {
2192
+ // Too many start attempts
2193
+ this.sessionStorage.removeItem(storeKeyFlowStarted);
2194
+ throw new Error('could not start authentication flow, too many attempts');
2195
+ }
2196
+ this.sessionStorage.setItem(storeKeyFlowStarted, (count + 1).toString());
2197
+ }
2121
2198
  /**
2122
2199
  * Exchanges authorization code for access and refresh tokens
2123
2200
  */
@@ -2141,6 +2218,10 @@ class Auth {
2141
2218
  */
2142
2219
  exchangeRefresh(refreshToken) {
2143
2220
  return __awaiter(this, void 0, void 0, function* () {
2221
+ /**
2222
+ * Finalize
2223
+ */
2224
+ this.completeFinalState();
2144
2225
  return this.oauth2token({
2145
2226
  // eslint-disable-next-line @typescript-eslint/camelcase
2146
2227
  refresh_token: refreshToken || '',
@@ -2184,7 +2265,7 @@ class Auth {
2184
2265
  this.startAuthenticationFlow();
2185
2266
  });
2186
2267
  }), 1000 * timeout);
2187
- this.sessionStorage.setItem(this.refreshTokenKey, oa2tkn.refresh_token);
2268
+ this.sessionStorage.setItem(storeKeyRefreshToken, oa2tkn.refresh_token);
2188
2269
  const u = new system.User({
2189
2270
  userID: oa2tkn.sub,
2190
2271
  name: oa2tkn.name,