feat: implement party user authentication system with signin/signup routes, JWT token validation, and frontend integration including mobile route configuration and API service updates

This commit is contained in:
louiscklaw
2025-06-18 01:14:05 +08:00
parent 4cf93f431e
commit c93b31b2f6
30 changed files with 1008 additions and 67 deletions

View File

@@ -1,3 +1,10 @@
/**
* Party User seed data generator
* Creates initial user accounts for development and testing
* Includes:
* - Fixed demo accounts (alice, demo)
* - Randomly generated test accounts with CJK locale data
*/
import { faker as enFaker } from '@faker-js/faker/locale/en_US';
import { faker as zhFaker } from '@faker-js/faker/locale/zh_CN';
import { faker as jaFaker } from '@faker-js/faker/locale/ja';
@@ -59,15 +66,18 @@ async function partyUser() {
update: {},
create: {
email: 'demo@minimals.cc',
name: 'Demo',
username: 'pudemo',
password: '@2Minimal',
//
username: 'pudemo',
name: 'Demo',
emailVerified: new Date(),
phoneNumber: '+85291234568',
company: 'helloworld company',
status: STATUS[1],
role: ROLE[1],
isVerified: true,
avatarUrl: 'https://images.unsplash.com/photo-1619970096024-c7b438a3b82a',
rank: 'user',
},
});

View File

@@ -1,5 +1,7 @@
###
# username and password ok
POST http://localhost:7272/api/auth/sign-in
content-type: application/json
@@ -9,7 +11,9 @@ content-type: application/json
}
###
# There is no user corresponding to the email address.
POST http://localhost:7272/api/auth/sign-in
content-type: application/json
@@ -19,7 +23,9 @@ content-type: application/json
}
###
# Wrong password
POST http://localhost:7272/api/auth/sign-in
content-type: application/json

View File

@@ -0,0 +1,68 @@
import type { User } from '@prisma/client';
import type { NextRequest } from 'next/server';
import { headers } from 'next/headers';
import { verify } from 'src/utils/jwt';
import { STATUS, response, handleError } from 'src/utils/response';
import { JWT_SECRET } from 'src/_mock/_auth';
import { getUserById } from 'src/app/services/user.service';
import { createAccessLog } from 'src/app/services/access-log.service';
import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
// ----------------------------------------------------------------------
// export const runtime = 'edge';
/**
* This API is used for demo purpose only
* You should use a real database
* You should hash the password before saving to database
* You should not save the password in the database
* You should not expose the JWT_SECRET in the client side
*/
const USER_TOKEN_CHECK_FAILED = 'user token check failed';
const INVALID_AUTH_TOKEN = 'Invalid authorization token';
const USER_ID_NOT_FOUND = 'userId not found';
const USER_TOKEN_OK = 'user token check ok';
const AUTHORIZATION_TOKEN_MISSING_OR_INVALID = 'Authorization token missing or invalid';
export async function GET(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
try {
const headersList = headers();
const authorization = headersList.get('authorization');
if (!authorization || !authorization.startsWith('Bearer ')) {
return response({ message: AUTHORIZATION_TOKEN_MISSING_OR_INVALID }, STATUS.UNAUTHORIZED);
}
const accessToken = `${authorization}`.split(' ')[1];
const data = await verify(accessToken, JWT_SECRET);
console.log(data.userId);
if (data.userId) {
// TODO: remove me
// const currentUser = _users.find((user) => user.id === data.userId);
const currentUser: User | null = await getUserById(data.userId);
if (!currentUser) {
createAccessLog('', USER_TOKEN_CHECK_FAILED, debug);
return response({ message: INVALID_AUTH_TOKEN }, STATUS.UNAUTHORIZED);
}
createAccessLog(currentUser.id, USER_TOKEN_OK, debug);
return response({ user: currentUser }, STATUS.OK);
} else {
return response({ message: USER_ID_NOT_FOUND }, STATUS.ERROR);
}
} catch (error) {
return handleError('[Auth] - Me', error);
}
}

View File

@@ -0,0 +1,25 @@
###
# username and password ok
GET http://localhost:7272/api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWJnbnUyengwMDBjaHEzaGZ3dmtjejlvIiwiaWF0IjoxNzQ4OTY0ODkyLCJleHAiOjE3NTAxNzQ0OTJ9.lo04laCxtm0IVeYaETEV3hXKyDmXPEn7SyWtY2VR4dI
###
# There is no user corresponding to the email address.
POST http://localhost:7272/api/auth/sign-in
content-type: application/json
{
"email": "demo@minimals1.cc",
"password": "@2Minimal"
}
###
# Wrong password
POST http://localhost:7272/api/auth/sign-in
content-type: application/json
{
"email": "demo@minimals.cc",
"password": "@2Min111imal"
}

View File

@@ -0,0 +1,10 @@
import type { NextRequest } from 'next/server';
/**
* Flattens a Next.js request object into a plain object of headers
* @param {NextRequest} req - The Next.js request object
* @returns {Record<string, string>} An object containing all request headers
*/
export function flattenNextjsRequest(req: NextRequest) {
return Object.fromEntries(req.headers.entries());
}

View File

@@ -0,0 +1,56 @@
// src/app/api/party-user-auth/sign-in/route1.ts
//
import type { NextRequest } from 'next/server';
import { sign } from 'src/utils/jwt';
import { STATUS, response, handleError } from 'src/utils/response';
import { JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth';
import { createAccessLog } from 'src/app/services/access-log.service';
import prisma from '../../../lib/prisma';
import { flattenNextjsRequest } from './flattenNextjsRequest';
// ----------------------------------------------------------------------
/**
* This API is used for demo purpose only
* You should use a real database
* You should hash the password before saving to database
* You should not save the password in the database
* You should not expose the JWT_SECRET in the client side
*/
const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.';
const ERR_WRONG_PASSWORD = 'Wrong password';
export async function POST(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
try {
const { email, password } = await req.json();
const currentUser = await prisma.partyUser.findFirst({ where: { email } });
if (!currentUser) {
await createAccessLog('', `user tried login with email ${email}`, { debug });
return response({ message: ERR_USER_NOT_FOUND }, STATUS.UNAUTHORIZED);
}
if (currentUser?.password !== password) {
await createAccessLog(currentUser.id, 'user logged with wrong password', { debug });
return response({ message: ERR_WRONG_PASSWORD }, STATUS.UNAUTHORIZED);
}
const accessToken = await sign({ userId: currentUser?.id }, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN,
});
await createAccessLog(currentUser.id, 'access granted', { debug });
return response({ user: currentUser, accessToken }, STATUS.OK);
} catch (error) {
await createAccessLog('', 'attempted login but failed', { debug, error });
return handleError('Auth - Sign in', error);
}
}

View File

@@ -0,0 +1,37 @@
# REQ0188 frontend party-user
###
# username and password ok
POST http://localhost:7272/api/party-user-auth/sign-in
content-type: application/json
{
"email": "demo@minimals.cc",
"password": "@2Minimal"
}
###
# There is no user corresponding to the email address.
POST http://localhost:7272/api/party-user-auth/sign-in
content-type: application/json
{
"email": "demo@minimals1.cc",
"password": "@2Minimal"
}
###
# Wrong password
POST http://localhost:7272/api/party-user-auth/sign-in
content-type: application/json
{
"email": "demo@minimals.cc",
"password": "@2Min111imal"
}

View File

@@ -0,0 +1,58 @@
import type { NextRequest } from 'next/server';
import { sign } from 'src/utils/jwt';
import { STATUS, response, handleError } from 'src/utils/response';
import { _users, JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth';
// ----------------------------------------------------------------------
/**
* This API is used for demo purpose only
* You should use a real database
* You should hash the password before saving to database
* You should not save the password in the database
* You should not expose the JWT_SECRET in the client side
*/
export const runtime = 'edge';
export async function POST(req: NextRequest) {
try {
const { email, password, firstName, lastName } = await req.json();
const userExists = _users.find((user) => user.email === email);
if (userExists) {
return response({ message: 'There already exists an account with the given email address.' }, STATUS.CONFLICT);
}
const newUser = {
id: _users[0].id,
displayName: `${firstName} ${lastName}`,
email,
password,
photoURL: '',
phoneNumber: '',
country: '',
address: '',
state: '',
city: '',
zipCode: '',
about: '',
role: 'user',
isPublic: true,
};
const accessToken = await sign({ userId: newUser.id }, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN,
});
// Push new user to database
_users.push(newUser);
return response({ user: newUser, accessToken }, STATUS.OK);
} catch (error) {
return handleError('Auth - Sign up', error);
}
}