blob: 45f78dd57cf6daf0bb6562f467ece68f355ddd44 [file] [log] [blame]
Ziwei Wang45baf532023-02-16 15:41:49 -05001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18import { useMutation, useQuery } from '@tanstack/react-query';
19import axios from 'axios';
20import { passwordStrength } from 'check-password-strength';
21import { AccessToken } from 'jami-web-common';
22import { HttpStatusCode } from 'jami-web-common';
23import { useContext } from 'react';
24import { useNavigate } from 'react-router-dom';
25
26import { NameStatus } from '../components/Input';
27import { AlertSnackbarContext } from '../contexts/AlertSnackbarProvider';
28import { PasswordStrength } from '../enums/passwordStrength';
29import { apiUrl } from '../utils/constants';
30import { jamiUsernamePattern } from '../utils/constants';
31interface PasswordStrengthResult {
32 id: number;
33 value: string;
34 contains: string[];
35 length: number;
36}
37
38interface LoginData {
39 username: string;
40 password: string;
41 isJams: boolean;
42}
43
44interface RegisterData {
45 username: string;
46 password: string;
47}
48
49export interface PasswordCheckResult {
50 strong: boolean;
51 valueCode: StrengthValueCode;
52}
53
54export type StrengthValueCode = 'default' | 'too_weak' | 'weak' | 'medium' | 'strong';
55export type LoginMethod = 'Jami' | 'JAMS';
56
57const idToStrengthValueCode: StrengthValueCode[] = ['too_weak', 'weak', 'medium', 'strong'];
58
59export const useCheckIfUsernameIsRegisteredQuery = (username: string) => {
60 return useQuery({
61 queryKey: ['username', username],
62 queryFn: async () => {
63 const res = await axios.get<NameStatus>(`/ns/username/availability/${username}`, {
64 baseURL: apiUrl,
65 });
66 return { responseMessage: res.data, statusCode: res.status };
67 },
68 enabled: jamiUsernamePattern.test(username),
69 });
70};
71
72export function checkPasswordStrength(password: string): PasswordCheckResult {
73 const strengthResult: PasswordStrengthResult = passwordStrength(password);
74
75 return {
76 strong: strengthResult.id === PasswordStrength.Strong.valueOf(),
77 valueCode: idToStrengthValueCode[strengthResult.id] ?? 'default',
78 };
79}
80
81export const useRegisterMutation = () => {
82 const { setAlertContent } = useContext(AlertSnackbarContext);
83 return useMutation({
84 mutationKey: ['user', 'register'],
85 mutationFn: async (registerData: RegisterData) => {
86 const response = await axios.post(
87 '/auth/new-account',
88 { username: registerData.username, password: registerData.password },
89 { baseURL: apiUrl }
90 );
91 return response;
92 },
93 onSuccess: (response) => {
94 if (response.status === HttpStatusCode.Created) {
95 setAlertContent({ messageI18nKey: 'registration_success', severity: 'success', alertOpen: true });
96 }
97 },
98 onError: (e: any) => {
99 const { status } = e.response;
100 if (status === HttpStatusCode.BadRequest) {
101 //TODO: more than one bad request response defined in the server is missing credentials. add the response message to the locale.
102 } else if (status === HttpStatusCode.Conflict) {
103 //TODO: there are two different conflict responses that could be returned by the server, use message to differentiate them?
104 } else if (status === HttpStatusCode.Unauthorized) {
105 //TODO: this is a response for JAMS, add message to the locale
106 } else {
107 setAlertContent({ messageI18nKey: 'unknown_error_alert', severity: 'error', alertOpen: true });
108 }
109 },
110 });
111};
112
113export const useLoginMutation = () => {
114 const navigate = useNavigate();
115 const { setAlertContent, closeAlert } = useContext(AlertSnackbarContext);
116 return useMutation({
117 mutationKey: ['user', 'login'],
118 mutationFn: async (loginData: LoginData) => {
119 const { data } = await axios.post<AccessToken>(
120 '/auth/login',
121 { username: loginData.username, password: loginData.password, isJams: loginData.isJams },
122 { baseURL: apiUrl }
123 );
124 return data;
125 },
126 onMutate: () => {
127 closeAlert();
128 },
129 onSuccess: (response) => {
130 setAccessToken(response.accessToken);
131 navigate('/conversation', { replace: true });
132 },
133 onError: (e: any) => {
134 //e: any is a simple workaround for type check since the error is of type unknown and we can't reference a property on an unknown
135 const { status } = e.response;
136 if (status === HttpStatusCode.BadRequest) {
137 //TODO: the only bad request response defined in the server is missing credentials. add the response message to the locale.
138 //continue when the auth flow is clear
139 } else if (status === HttpStatusCode.NotFound) {
140 //TODO: there are two different not found responses that could be returned by the server, use message to differentiate them?
141 //continue when the auth flow is clear
142 } else if (status === HttpStatusCode.Unauthorized) {
143 setAlertContent({ messageI18nKey: 'login_invalid_password', severity: 'error', alertOpen: true });
144 } else {
145 setAlertContent({ messageI18nKey: 'unknown_error_alert', severity: 'error', alertOpen: true });
146 }
147 },
148 });
149};
150
151export function getAccessToken(): string | undefined {
152 return localStorage.getItem('accessToken') ?? undefined;
153}
154
155export function setAccessToken(accessToken: string): void {
156 localStorage.setItem('accessToken', accessToken);
157}