JWT impl.

This commit is contained in:
Paulo Gustavo Veiga 2024-02-06 21:48:04 -08:00
parent 7a4b5212d9
commit cbca2e6184
8 changed files with 192 additions and 58 deletions

View File

@ -39,7 +39,8 @@
"react-query": "^3.39.1", "react-query": "^3.39.1",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-router-dom": "^6.4.3", "react-router-dom": "^6.4.3",
"styled-components": "^5.3.6" "styled-components": "^5.3.6",
"universal-cookie": "^7.0.2"
}, },
"devDependencies": { "devDependencies": {
"@formatjs/cli": "^6.0.4", "@formatjs/cli": "^6.0.4",

View File

@ -7,7 +7,8 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="<%=PUBLIC_URL%>/favicon.ico" /> <link rel="icon" href="<%=PUBLIC_URL%>/favicon.ico" />
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;600&display=swap" rel="stylesheet" rel="preload"/> <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;600&display=swap"
rel="stylesheet" rel="preload" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
@ -26,6 +27,20 @@
<meta property="og:url" content="https://www.wisemapping.com" /> <meta property="og:url" content="https://www.wisemapping.com" />
<meta property="og:site_name" content="WiseMapping" /> <meta property="og:site_name" content="WiseMapping" />
<script>
if ('<%=CLIENT_TYPE%>' === 'rest');
{
window.serverconfig = {
apiBaseUrl: 'http://localhost:3000',
analyticsAccount: 'G-RSDEJH16YM',
clientType: 'rest',
recaptcha2Enabled: true,
recaptcha2SiteKey: '6Lcat08kAAAAAIP-HjhzIa-Yq21PHgGa_ADWc-Ro',
googleOauth2Url: 'https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=https://app.wisemapping.com/c/registration-google&prompt=consent&response_type=code&client_id=625682766634-cocbbbbb403iuvps1evecdk6d7phvbkf.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&access_type=offline&state=wisemapping&include_granted_scopes=true'
};
}
window.errorMvcView = '';
</script>
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a

View File

@ -1,5 +1,10 @@
import { Locale, LocaleCode } from '../app-i18n'; import { Locale, LocaleCode } from '../app-i18n';
export type JwtAuth = {
email: string;
password: string;
};
export type NewUser = { export type NewUser = {
email: string; email: string;
firstname: string; firstname: string;
@ -85,6 +90,8 @@ export type ForgotPasswordResult = {
}; };
interface Client { interface Client {
login(auth: JwtAuth): Promise<void>;
deleteAccount(): Promise<void>; deleteAccount(): Promise<void>;
importMap(model: ImportMapInfo): Promise<number>; importMap(model: ImportMapInfo): Promise<number>;
createMap(map: BasicMapInfo): Promise<number>; createMap(map: BasicMapInfo): Promise<number>;

View File

@ -26,8 +26,10 @@ import Client, {
Permission, Permission,
Oauth2CallbackResult, Oauth2CallbackResult,
ForgotPasswordResult, ForgotPasswordResult,
JwtAuth,
} from '..'; } from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n'; import { LocaleCode, localeFromStr } from '../../app-i18n';
import Cookies from 'universal-cookie';
const label1: Label = { const label1: Label = {
id: 1, id: 1,
@ -127,6 +129,18 @@ class MockClient implements Client {
this.labels = [label1, label2, label3]; this.labels = [label1, label2, label3];
} }
login(auth: JwtAuth): Promise<void> {
const cookies = new Cookies();
cookies.set('jwt-token-mock', auth.email, { path: '/' });
return Promise.resolve();
}
private _jwtToken(): string | undefined {
// Set cookie on session ...
const cookies = new Cookies();
return cookies.get('jwt-token-mock');
}
fetchStarred(id: number): Promise<boolean> { fetchStarred(id: number): Promise<boolean> {
return Promise.resolve(Boolean(this.maps.find((m) => m.id == id)?.starred)); return Promise.resolve(Boolean(this.maps.find((m) => m.id == id)?.starred));
} }

View File

@ -11,9 +11,11 @@ import Client, {
Permission, Permission,
Oauth2CallbackResult, Oauth2CallbackResult,
ForgotPasswordResult, ForgotPasswordResult,
JwtAuth,
} from '..'; } from '..';
import { getCsrfToken } from '../../../utils'; import { getCsrfToken } from '../../../utils';
import { LocaleCode, localeFromStr } from '../../app-i18n'; import { LocaleCode, localeFromStr } from '../../app-i18n';
import Cookies from 'universal-cookie';
export default class RestClient implements Client { export default class RestClient implements Client {
private baseUrl: string; private baseUrl: string;
@ -32,18 +34,64 @@ export default class RestClient implements Client {
constructor(baseUrl: string) { constructor(baseUrl: string) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.axios = axios.create({ maxRedirects: 0 }); this.axios = axios.create({ maxRedirects: 0 });
// Configure request interceptors ...
this.axios.interceptors.request.use((config) => {
if (config.headers) {
// JWT Token ...
const jwtToken = this._jwtToken();
if (jwtToken) {
config.headers['Authorization'] = jwtToken;
}
// Add Csrf token ...
const csrfToken = getCsrfToken(); const csrfToken = getCsrfToken();
if (csrfToken) { if (csrfToken) {
this.axios.defaults.headers['X-CSRF-TOKEN'] = csrfToken; config.headers['X-CSRF-TOKEN'] = csrfToken;
} else { } else {
console.warn('csrf token not found in html head'); console.warn('csrf token not found in html head');
} }
}
return config;
});
// Process response globally ...
this.axios.interceptors.response.use( this.axios.interceptors.response.use(
(r) => r, (response) => response,
(r) => this.checkResponseForSessionExpired(r), (respoonse) => this.checkResponseForSessionExpired(respoonse),
); );
} }
login(model: JwtAuth): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.post(`${this.baseUrl}/api/restful/authenticate`, model, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const token = response.data;
// Set jwt token on cookie ...
const cookies = new Cookies();
cookies.set('jwt-auth-token', token, { path: '/', maxAge: 604800 });
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
private _jwtToken(): string | null {
// Set cookie on session ...
const cookies = new Cookies();
const token = cookies.get('jwt-auth-token');
return token ? `Bearer ${token}` : null;
}
private _onSessionExpired: () => void; private _onSessionExpired: () => void;
onSessionExpired(callback?: () => void): () => void { onSessionExpired(callback?: () => void): () => void {
if (callback) { if (callback) {
@ -61,9 +109,12 @@ export default class RestClient implements Client {
deleteMapPermission(id: number, email: string): Promise<void> { deleteMapPermission(id: number, email: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.delete(`${this.baseUrl}/c/restful/maps/${id}/collabs?email=${encodeURIComponent(email)}`, { .delete(
`${this.baseUrl}/api/restful/maps/${id}/collabs?email=${encodeURIComponent(email)}`,
{
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) },
)
.then(() => { .then(() => {
success(); success();
}) })
@ -78,7 +129,7 @@ export default class RestClient implements Client {
fetchStarred(id: number): Promise<boolean> { fetchStarred(id: number): Promise<boolean> {
const handler = (success: (starred: boolean) => void, reject: (error: ErrorInfo) => void) => { const handler = (success: (starred: boolean) => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.get(`${this.baseUrl}/c/restful/maps/${id}/starred`, { .get(`${this.baseUrl}/api/restful/maps/${id}/starred`, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then((response) => { .then((response) => {
@ -98,7 +149,7 @@ export default class RestClient implements Client {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put( .put(
`${this.baseUrl}/c/restful/maps/${id}/collabs/`, `${this.baseUrl}/api/restful/maps/${id}/collabs/`,
{ {
message: message, message: message,
collaborations: permissions, collaborations: permissions,
@ -123,7 +174,7 @@ export default class RestClient implements Client {
reject: (error: ErrorInfo) => void, reject: (error: ErrorInfo) => void,
) => { ) => {
this.axios this.axios
.get(`${this.baseUrl}/c/restful/maps/${id}/collabs`, { .get(`${this.baseUrl}/api/restful/maps/${id}/collabs`, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then((response) => { .then((response) => {
@ -150,7 +201,7 @@ export default class RestClient implements Client {
deleteAccount(): Promise<void> { deleteAccount(): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.delete(`${this.baseUrl}/c/restful/account`, { .delete(`${this.baseUrl}/api/restful/account`, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
@ -167,11 +218,11 @@ export default class RestClient implements Client {
updateAccountInfo(firstname: string, lastname: string): Promise<void> { updateAccountInfo(firstname: string, lastname: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put(`${this.baseUrl}/c/restful/account/firstname`, firstname, { .put(`${this.baseUrl}/api/restful/account/firstname`, firstname, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
return this.axios.put(`${this.baseUrl}/c/restful/account/lastname`, lastname, { return this.axios.put(`${this.baseUrl}/api/restful/account/lastname`, lastname, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}); });
}) })
@ -190,7 +241,7 @@ export default class RestClient implements Client {
updateAccountPassword(pasword: string): Promise<void> { updateAccountPassword(pasword: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put(`${this.baseUrl}/c/restful/account/password`, pasword, { .put(`${this.baseUrl}/api/restful/account/password`, pasword, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
@ -207,7 +258,7 @@ export default class RestClient implements Client {
updateAccountLanguage(locale: LocaleCode): Promise<void> { updateAccountLanguage(locale: LocaleCode): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put(`${this.baseUrl}/c/restful/account/locale`, locale, { .put(`${this.baseUrl}/api/restful/account/locale`, locale, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
@ -229,7 +280,7 @@ export default class RestClient implements Client {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => { const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.post( .post(
`${this.baseUrl}/c/restful/maps?title=${encodeURIComponent( `${this.baseUrl}/api/restful/maps?title=${encodeURIComponent(
model.title, model.title,
)}&description=${encodeURIComponent(model.description ? model.description : '')}`, )}&description=${encodeURIComponent(model.description ? model.description : '')}`,
model.content, model.content,
@ -253,7 +304,7 @@ export default class RestClient implements Client {
reject: (error: ErrorInfo) => void, reject: (error: ErrorInfo) => void,
) => { ) => {
this.axios this.axios
.get(`${this.baseUrl}/c/restful/account`, { .get(`${this.baseUrl}/api/restful/account`, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
@ -278,7 +329,7 @@ export default class RestClient implements Client {
deleteMaps(ids: number[]): Promise<void> { deleteMaps(ids: number[]): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.delete(`${this.baseUrl}/c/restful/maps/batch?ids=${ids.join()}`, { .delete(`${this.baseUrl}/api/restful/maps/batch?ids=${ids.join()}`, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
@ -296,7 +347,7 @@ export default class RestClient implements Client {
updateMapToPublic(id: number, isPublic: boolean): Promise<void> { updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put(`${this.baseUrl}/c/restful/maps/${id}/publish`, isPublic.toString(), { .put(`${this.baseUrl}/api/restful/maps/${id}/publish`, isPublic.toString(), {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
@ -313,7 +364,7 @@ export default class RestClient implements Client {
revertHistory(id: number, hid: number): Promise<void> { revertHistory(id: number, hid: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.post(`${this.baseUrl}/c/restful/maps/${id}/history/${hid}`, null, { .post(`${this.baseUrl}/api/restful/maps/${id}/history/${hid}`, null, {
headers: { 'Content-Type': 'text/pain' }, headers: { 'Content-Type': 'text/pain' },
}) })
.then(() => { .then(() => {
@ -333,7 +384,7 @@ export default class RestClient implements Client {
reject: (error: ErrorInfo) => void, reject: (error: ErrorInfo) => void,
) => { ) => {
this.axios this.axios
.get(`${this.baseUrl}/c/restful/maps/${id}/history/`, { .get(`${this.baseUrl}/api/restful/maps/${id}/history/`, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
@ -358,12 +409,12 @@ export default class RestClient implements Client {
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> { renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put(`${this.baseUrl}/c/restful/maps/${id}/title`, basicInfo.title, { .put(`${this.baseUrl}/api/restful/maps/${id}/title`, basicInfo.title, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
return this.axios.put( return this.axios.put(
`${this.baseUrl}/c/restful/maps/${id}/description`, `${this.baseUrl}/api/restful/maps/${id}/description`,
basicInfo.description || ' ', basicInfo.description || ' ',
{ headers: { 'Content-Type': 'text/plain' } }, { headers: { 'Content-Type': 'text/plain' } },
); );
@ -385,10 +436,10 @@ export default class RestClient implements Client {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => { const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.post( .post(
`${this.baseUrl}/c/restful/maps?title=${encodeURIComponent( `${this.baseUrl}/api/restful/maps?title=${encodeURIComponent(
model.title, model.title,
)}&description=${encodeURIComponent(model.description ? model.description : '')}`, )}&description=${encodeURIComponent(model.description ? model.description : '')}`,
null, undefined,
{ headers: { 'Content-Type': 'application/json' } }, { headers: { 'Content-Type': 'application/json' } },
) )
.then((response) => { .then((response) => {
@ -409,7 +460,7 @@ export default class RestClient implements Client {
reject: (error: ErrorInfo) => void, reject: (error: ErrorInfo) => void,
) => { ) => {
this.axios this.axios
.get(`${this.baseUrl}/c/restful/maps/`, { .get(`${this.baseUrl}/api/restful/maps/`, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
@ -443,7 +494,7 @@ export default class RestClient implements Client {
registerNewUser(user: NewUser): Promise<void> { registerNewUser(user: NewUser): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.post(`${this.baseUrl}/service/users/`, JSON.stringify(user), { .post(`${this.baseUrl}/api/restful/users/`, JSON.stringify(user), {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then(() => { .then(() => {
@ -461,7 +512,7 @@ export default class RestClient implements Client {
deleteMap(id: number): Promise<void> { deleteMap(id: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.delete(`${this.baseUrl}/c/restful/maps/${id}`, { .delete(`${this.baseUrl}/api/restful/maps/${id}`, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then(() => { .then(() => {
@ -482,7 +533,7 @@ export default class RestClient implements Client {
) => { ) => {
this.axios this.axios
.put( .put(
`${this.baseUrl}/service/users/resetPassword?email=${encodeURIComponent(email)}`, `${this.baseUrl}/api/restful/users/resetPassword?email=${encodeURIComponent(email)}`,
null, null,
{ {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -504,7 +555,7 @@ export default class RestClient implements Client {
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> { duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => { const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.post(`${this.baseUrl}/c/restful/maps/${id}`, JSON.stringify(basicInfo), { .post(`${this.baseUrl}/api/restful/maps/${id}`, JSON.stringify(basicInfo), {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
@ -523,7 +574,7 @@ export default class RestClient implements Client {
updateStarred(id: number, starred: boolean): Promise<void> { updateStarred(id: number, starred: boolean): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put(`${this.baseUrl}/c/restful/maps/${id}/starred`, starred.toString(), { .put(`${this.baseUrl}/api/restful/maps/${id}/starred`, starred.toString(), {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
@ -541,7 +592,7 @@ export default class RestClient implements Client {
fetchLabels(): Promise<Label[]> { fetchLabels(): Promise<Label[]> {
const handler = (success: (labels: Label[]) => void, reject: (error: ErrorInfo) => void) => { const handler = (success: (labels: Label[]) => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.get(`${this.baseUrl}/c/restful/labels/`, { .get(`${this.baseUrl}/api/restful/labels/`, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
@ -569,7 +620,7 @@ export default class RestClient implements Client {
const handler = (success: (labelId: number) => void, reject: (error: ErrorInfo) => void) => { const handler = (success: (labelId: number) => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.post( .post(
`${this.baseUrl}/c/restful/labels`, `${this.baseUrl}/api/restful/labels`,
JSON.stringify({ title, color, iconName: 'smile' }), JSON.stringify({ title, color, iconName: 'smile' }),
{ {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -590,7 +641,7 @@ export default class RestClient implements Client {
deleteLabel(id: number): Promise<void> { deleteLabel(id: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.delete(`${this.baseUrl}/c/restful/labels/${id}`) .delete(`${this.baseUrl}/api/restful/labels/${id}`)
.then(() => { .then(() => {
success(); success();
}) })
@ -605,7 +656,7 @@ export default class RestClient implements Client {
addLabelToMap(labelId: number, mapId: number): Promise<void> { addLabelToMap(labelId: number, mapId: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.post(`${this.baseUrl}/c/restful/maps/${mapId}/labels`, JSON.stringify(labelId), { .post(`${this.baseUrl}/api/restful/maps/${mapId}/labels`, JSON.stringify(labelId), {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then(() => { .then(() => {
@ -622,7 +673,7 @@ export default class RestClient implements Client {
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> { deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`) .delete(`${this.baseUrl}/api/restful/maps/${mapId}/labels/${labelId}`)
.then(() => { .then(() => {
success(); success();
}) })
@ -640,7 +691,7 @@ export default class RestClient implements Client {
reject: (error: ErrorInfo) => void, reject: (error: ErrorInfo) => void,
) => { ) => {
this.axios this.axios
.post(`${this.baseUrl}/service/oauth2/googlecallback?code=${code}`, { .post(`${this.baseUrl}/api/restful/oauth2/googlecallback?code=${code}`, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
@ -661,7 +712,7 @@ export default class RestClient implements Client {
confirmAccountSync(email: string, code: string): Promise<void> { confirmAccountSync(email: string, code: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios this.axios
.put(`${this.baseUrl}/service/oauth2/confirmaccountsync?email=${email}&code=${code}`, { .put(`${this.baseUrl}/api/restful/oauth2/confirmaccountsync?email=${email}&code=${code}`, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then(() => { .then(() => {

View File

@ -1,6 +1,6 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl'; import { FormattedMessage, useIntl } from 'react-intl';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink, useNavigate } from 'react-router-dom';
import Header from '../layout/header'; import Header from '../layout/header';
import Footer from '../layout/footer'; import Footer from '../layout/footer';
import SubmitButton from '../form/submit-button'; import SubmitButton from '../form/submit-button';
@ -15,6 +15,17 @@ import Separator from '../common/separator';
import GoogleButton from '../common/google-button'; import GoogleButton from '../common/google-button';
import AppConfig from '../../classes/app-config'; import AppConfig from '../../classes/app-config';
import CSRFInput from '../common/csrf-input'; import CSRFInput from '../common/csrf-input';
import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../classes/client';
import { activeInstance } from '../../redux/clientSlice';
export type Model = {
email: string;
password: string;
};
const defaultModel: Model = { email: '', password: '' };
const LoginError = () => { const LoginError = () => {
// @Todo: This must be reviewed to be based on navigation state. // @Todo: This must be reviewed to be based on navigation state.
@ -44,6 +55,9 @@ const LoginError = () => {
const LoginPage = (): React.ReactElement => { const LoginPage = (): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const [model, setModel] = useState<Model>(defaultModel);
const client: Client = useSelector(activeInstance);
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
document.title = intl.formatMessage({ document.title = intl.formatMessage({
@ -53,6 +67,29 @@ const LoginPage = (): React.ReactElement => {
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Login' }); ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Login' });
}, []); }, []);
const mutation = useMutation<void, ErrorInfo, Model>(
(model: Model) => client.login({ ...model }),
{
onSuccess: () => navigate('/c/maps/'),
onError: (error) => {
console.log(error);
},
},
);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
mutation.mutate(model);
event.preventDefault();
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof Model]: value });
};
return ( return (
<div> <div>
<Header type="only-signup" /> <Header type="only-signup" />
@ -69,10 +106,11 @@ const LoginPage = (): React.ReactElement => {
<LoginError /> <LoginError />
<FormControl> <FormControl>
<form action="/c/perform-login" method="POST"> <form onSubmit={handleOnSubmit}>
<CSRFInput /> <CSRFInput />
<Input <Input
name="username" onChange={handleOnChange}
name="email"
type="email" type="email"
label={intl.formatMessage({ label={intl.formatMessage({
id: 'login.email', id: 'login.email',
@ -82,6 +120,7 @@ const LoginPage = (): React.ReactElement => {
autoComplete="email" autoComplete="email"
/> />
<Input <Input
onChange={handleOnChange}
name="password" name="password"
type="password" type="password"
label={intl.formatMessage({ label={intl.formatMessage({
@ -91,12 +130,6 @@ const LoginPage = (): React.ReactElement => {
required required
autoComplete="current-password" autoComplete="current-password"
/> />
<div>
<input name="remember-me" id="remember-me" type="checkbox" />
<label htmlFor="remember-me">
<FormattedMessage id="login.remberme" defaultMessage="Remember me" />
</label>
</div>
<SubmitButton <SubmitButton
value={intl.formatMessage({ value={intl.formatMessage({
id: 'login.signin', id: 'login.signin',

View File

@ -41,9 +41,9 @@ const RegistrationForm = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const intl = useIntl(); const intl = useIntl();
const Client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const mutation = useMutation<void, ErrorInfo, Model>( const mutation = useMutation<void, ErrorInfo, Model>(
(model: Model) => Client.registerNewUser({ ...model }), (model: Model) => client.registerNewUser({ ...model }),
{ {
onSuccess: () => navigate('/c/registration-success'), onSuccess: () => navigate('/c/registration-success'),
onError: (error) => { onError: (error) => {

View File

@ -10,6 +10,18 @@ module.exports = merge(common, {
devServer: { devServer: {
port: 3000, port: 3000,
hot: true, hot: true,
proxy: {
'/api': {
target: {
host: "0.0.0.0",
protocol: 'http:',
port: 8080
},
pathRewrite: {
'^/api': ''
}
},
},
historyApiFallback: { historyApiFallback: {
rewrites: [{ from: /^\/c\//, to: '/index.html' }], rewrites: [{ from: /^\/c\//, to: '/index.html' }],
}, },
@ -19,6 +31,7 @@ module.exports = merge(common, {
template: path.join(__dirname, 'public/index.html'), template: path.join(__dirname, 'public/index.html'),
templateParameters: { templateParameters: {
PUBLIC_URL: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'http://localhost:3000', PUBLIC_URL: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'http://localhost:3000',
CLIENT_TYPE: process.env.CLIENT_TYPE ? process.env.CLIENT_TYPE : 'mock'
}, },
base: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'http://localhost:3000', base: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'http://localhost:3000',
}), }),