feat(users): add reset password functionality for users and implement forgot password page

This commit is contained in:
Tran Anh Tuan
2026-02-03 17:33:47 +07:00
parent 9bc15192ec
commit dca363275e
35 changed files with 1592 additions and 321 deletions

View File

@@ -1,7 +1,37 @@
export function parseJwt(token: string) {
export function parseAccessToken(
token: string,
): MasterModel.TokenParsedTransformed | null {
if (!token) return null;
const base64Url = token.split('.')[1];
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join(''),
);
const accessToken: MasterModel.TokenParsed = JSON.parse(jsonPayload);
return {
expriresAt: accessToken.exp,
issuedAt: accessToken.iat,
issuer: accessToken.iss,
subject: accessToken.sub,
issuerId: accessToken.issuer_id,
type: accessToken.type,
purpose: 'access',
};
} catch (error) {
return null;
}
}
export function parseRefreshToken(
refreshToken: string,
): MasterModel.RefreshTokenParsedTransformed | null {
if (!refreshToken) return null;
const base64Url = refreshToken.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
@@ -9,16 +39,27 @@ export function parseJwt(token: string) {
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join(''),
);
return JSON.parse(jsonPayload);
const refreshTokenParsed: MasterModel.RefreshTokenParsed =
JSON.parse(jsonPayload);
return {
expriresAt: refreshTokenParsed.exp,
issuedAt: refreshTokenParsed.iat,
issuer: refreshTokenParsed.iss,
subject: refreshTokenParsed.sub,
issuerId: refreshTokenParsed.issuer_id,
type: refreshTokenParsed.type,
purpose: 'refresh',
jwtID: refreshTokenParsed.jti,
};
}
export function checkTokenExpired(token: string): boolean {
const parsed = parseJwt(token);
const { exp } = parsed;
export function checkRefreshTokenExpired(token: string): boolean {
const parsed = parseRefreshToken(token);
if (!parsed) return true;
const { expriresAt } = parsed;
const now = Math.floor(Date.now() / 1000);
const oneHour = 60 * 60;
if (exp - now < oneHour) {
const fiveMinutes = 60 * 5; // 5 minutes
if (expriresAt - now < fiveMinutes) {
return true;
}
return false;

View File

@@ -14,3 +14,15 @@ export const getLogoImage = () => {
return smatecLogo;
}
};
export const getDomainTitle = () => {
switch (process.env.DOMAIN_ENV) {
case 'gms':
return 'gms.title';
case 'sgw':
return 'sgw.title';
case 'spole':
return 'spole.title';
default:
return 'Smatec Master';
}
};

View File

@@ -1,15 +1,27 @@
import { TOKEN } from '@/constants';
import { ACCESS_TOKEN, REFRESH_TOKEN } from '@/constants';
export function getToken(): string {
return localStorage.getItem(TOKEN) || '';
export function getAccessToken(): string {
return localStorage.getItem(ACCESS_TOKEN) || '';
}
export function setToken(token: string) {
localStorage.setItem(TOKEN, token);
export function setAccessToken(accessToken: string) {
localStorage.setItem(ACCESS_TOKEN, accessToken);
}
export function removeToken() {
localStorage.removeItem(TOKEN);
export function removeAccessToken() {
localStorage.removeItem(ACCESS_TOKEN);
}
export function getRefreshToken(): string {
return localStorage.getItem(REFRESH_TOKEN) || '';
}
export function setRefreshToken(refreshToken: string) {
localStorage.setItem(REFRESH_TOKEN, refreshToken);
}
export function removeRefreshToken() {
localStorage.removeItem(REFRESH_TOKEN);
}
export function getBrowserId() {

View File

@@ -1,5 +1,5 @@
import ReconnectingWebSocket from 'reconnecting-websocket';
import { getToken } from './storage';
import { getAccessToken } from './storage';
type MessageHandler = (data: any) => void;
@@ -16,7 +16,7 @@ class WSClient {
if (this.ws) return;
let token = '';
if (isAuthenticated) {
token = getToken();
token = getAccessToken();
}
let wsUrl = url;
if (url.startsWith('/')) {