password expiry policy added
This commit is contained in:
parent
dc0b46fbd9
commit
1bbe205a79
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "krishna-sales-pwa",
|
"name": "krishna-sales-pwa",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@ -271,5 +271,5 @@ export const HIDDEN_FORM_FIELDS = [
|
|||||||
export const SELECT_OPTION_LIMIT = 200;
|
export const SELECT_OPTION_LIMIT = 200;
|
||||||
export const PDF_MAX_SKUS_PER_CHUNK = 50;
|
export const PDF_MAX_SKUS_PER_CHUNK = 50;
|
||||||
|
|
||||||
export const APP_VERSION = '1.0.4';
|
export const APP_VERSION = '1.0.5';
|
||||||
export const FORM_DRAFT_EXPIRY_MS = 30 * 60 * 1000;
|
export const FORM_DRAFT_EXPIRY_MS = 30 * 60 * 1000;
|
||||||
|
|||||||
@ -81,6 +81,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
login: async (email, password, orgId) => {
|
login: async (email, password, orgId) => {
|
||||||
const res = await loginAll(email, password, orgId);
|
const res = await loginAll(email, password, orgId);
|
||||||
applyUserSession(res);
|
applyUserSession(res);
|
||||||
|
return (res.user?.password_status as PasswordStatus | undefined) ?? null;
|
||||||
},
|
},
|
||||||
completePasswordChange: async (email, currentPassword, newPassword, orgId) => {
|
completePasswordChange: async (email, currentPassword, newPassword, orgId) => {
|
||||||
const res = await completePasswordChangeAll(email, currentPassword, newPassword, orgId);
|
const res = await completePasswordChangeAll(email, currentPassword, newPassword, orgId);
|
||||||
|
|||||||
@ -13,7 +13,8 @@ export interface AuthValue {
|
|||||||
roles: string[];
|
roles: string[];
|
||||||
userEmail: string | null;
|
userEmail: string | null;
|
||||||
passwordStatus: PasswordStatus | null;
|
passwordStatus: PasswordStatus | null;
|
||||||
login: (email: string, password: string, orgId?: string) => Promise<void>;
|
/** Resolves with the account's password status so the caller can react to must_change. */
|
||||||
|
login: (email: string, password: string, orgId?: string) => Promise<PasswordStatus | null>;
|
||||||
completePasswordChange: (email: string, currentPassword: string, newPassword: string, orgId?: string) => Promise<void>;
|
completePasswordChange: (email: string, currentPassword: string, newPassword: string, orgId?: string) => Promise<void>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useState, type FormEvent } from 'react';
|
import { useState, type FormEvent } from 'react';
|
||||||
import { Navigate, useNavigate, Link } from 'react-router-dom';
|
import { Navigate, useNavigate, Link } from 'react-router-dom';
|
||||||
import { Eye, EyeOff, Lock, Mail, AlertCircle, ArrowLeft, ArrowRight } from 'lucide-react';
|
import { Eye, EyeOff, Lock, Mail, AlertCircle, ArrowLeft, ArrowRight } from 'lucide-react';
|
||||||
|
import { changePassword } from '../api/auth';
|
||||||
import { useAuth, type PasswordChangeRequiredInfo } from '../auth/context';
|
import { useAuth, type PasswordChangeRequiredInfo } from '../auth/context';
|
||||||
import { Button } from '../components/buttons';
|
import { Button } from '../components/buttons';
|
||||||
import { AuthShell } from '../components/auth/AuthShell';
|
import { AuthShell } from '../components/auth/AuthShell';
|
||||||
@ -15,7 +16,7 @@ import {
|
|||||||
|
|
||||||
/** Login gate. Redirects to /daily once authenticated. */
|
/** Login gate. Redirects to /daily once authenticated. */
|
||||||
export function LoginPage() {
|
export function LoginPage() {
|
||||||
const { authed, login, completePasswordChange } = useAuth();
|
const { authed, login, logout, completePasswordChange } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
@ -37,9 +38,13 @@ export function LoginPage() {
|
|||||||
confirmPassword?: boolean;
|
confirmPassword?: boolean;
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
|
// True when sign-in succeeded but the account is flagged must_change: the session exists,
|
||||||
|
// so the new password is set via the authenticated endpoint rather than change-expired-password.
|
||||||
|
const [sessionChange, setSessionChange] = useState(false);
|
||||||
|
|
||||||
const policy = usePasswordPolicy(!!changeRequired);
|
const policy = usePasswordPolicy(!!changeRequired);
|
||||||
|
|
||||||
if (authed) return <Navigate to="/daily" replace />;
|
if (authed && !changeRequired) return <Navigate to="/daily" replace />;
|
||||||
|
|
||||||
async function submit(e: FormEvent) {
|
async function submit(e: FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -64,7 +69,15 @@ export function LoginPage() {
|
|||||||
|
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
await login(email, password);
|
const status = await login(email, password);
|
||||||
|
if (status?.must_change || (status?.days_left !== undefined && status.days_left <= 0)) {
|
||||||
|
setSessionChange(true);
|
||||||
|
setChangeRequired({
|
||||||
|
message: 'Your password has expired. Please choose a new password to continue.',
|
||||||
|
reason: 'expired',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
navigate('/daily', { replace: true });
|
navigate('/daily', { replace: true });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// Backend signals that password policy or expiry requires a new password first
|
// Backend signals that password policy or expiry requires a new password first
|
||||||
@ -130,7 +143,15 @@ export function LoginPage() {
|
|||||||
|
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
|
if (sessionChange) {
|
||||||
|
// Changing the password invalidates the current session, so sign in again with the new one.
|
||||||
|
await changePassword(undefined, newPassword);
|
||||||
|
await login(email, newPassword);
|
||||||
|
setSessionChange(false);
|
||||||
|
setChangeRequired(null);
|
||||||
|
} else {
|
||||||
await completePasswordChange(email, password, newPassword);
|
await completePasswordChange(email, password, newPassword);
|
||||||
|
}
|
||||||
navigate('/daily', { replace: true });
|
navigate('/daily', { replace: true });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.message ?? 'Could not update password. Please try again.');
|
setError(err?.message ?? 'Could not update password. Please try again.');
|
||||||
@ -140,6 +161,10 @@ export function LoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function backToSignIn() {
|
function backToSignIn() {
|
||||||
|
if (sessionChange) {
|
||||||
|
logout();
|
||||||
|
setSessionChange(false);
|
||||||
|
}
|
||||||
setChangeRequired(null);
|
setChangeRequired(null);
|
||||||
setNewPassword('');
|
setNewPassword('');
|
||||||
setConfirmPassword('');
|
setConfirmPassword('');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user