Files
bobu/functions/src/auth/registerUser.ts
2025-06-03 21:13:56 +09:00

275 lines
8.9 KiB
TypeScript

//NEEDS WORKS ON < PAST CODE >
import { onRequest } from 'firebase-functions/v2/https';
import { logger } from 'firebase-functions/v2';
import { corsMiddlewareHandler, db, FieldValue } from '../config';
import { toErrorWithMessage } from '../utils';
import * as admin from 'firebase-admin';
// --- Function 1: registerNewUser (Admin/Role-Protected Registration) ---
export const registerNewUserV2 = onRequest(async (request, response) => {
return corsMiddlewareHandler(request, response, async () => {
logger.info('registerNewUser: Function invoked via CORS Middleware.');
const token = request.get('Authorization')?.split('Bearer ')[1];
if (token == null || token === '') {
logger.warn('registerNewUser: No auth token provided.');
response.status(401).send({
error: 'unauthenticated',
message: 'You must be logged in to register a user.',
});
return;
}
let decodedToken;
try {
decodedToken = await admin.auth().verifyIdToken(token);
logger.info(
`registerNewUser: Token verified for admin UID: ${decodedToken.uid}`,
);
} catch (error: unknown) {
const errorMsg = toErrorWithMessage(error).message;
logger.error(`registerNewUser: Invalid token: ${errorMsg}`);
response.status(401).send({
error: 'unauthenticated',
message: 'Invalid token.',
details: errorMsg,
});
return;
}
// Fetch the user's role
let userRole = 0;
try {
const userDoc = await db.collection('users').doc(decodedToken.uid).get();
if (userDoc.exists) {
userRole = userDoc.data()?.role || 0;
logger.info(`registerNewUser: Requesting admin role is ${userRole}.`);
} else {
logger.warn(
`registerNewUser: Admin user document not found for UID: ${decodedToken.uid}`,
);
}
} catch (error: unknown) {
const errorMsg = toErrorWithMessage(error).message;
logger.error(
`registerNewUser: Failed to fetch admin role for UID ${decodedToken.uid}: ${errorMsg}`,
);
response.status(500).send({
error: 'internal',
message: "Failed to fetch user's role.",
details: errorMsg,
});
return;
}
// Role Check
const requiredAdminRole = 5;
if (userRole < requiredAdminRole) {
logger.warn(
`registerNewUser: Permission denied for UID ${decodedToken.uid} with role ${userRole}.`,
);
response.status(403).send({
error: 'permission-denied',
message: 'Insufficient role level to register a new user.',
});
return;
}
logger.info(
`registerNewUser: Permission granted for UID ${decodedToken.uid}. Proceeding.`,
);
// Proceed with user registration
try {
const { email, password, name, phone, membership, ...rest } =
request.body;
if (!email || !password || !name) {
logger.error(
'registerNewUser: Missing required fields (email, password, name).',
);
response.status(400).send({
error: 'missing-fields',
message: 'Missing required fields: email, password, name.',
});
return;
}
logger.info(
`registerNewUser: Attempting to create user with email: ${email}`,
);
// Corrected: Pass user data object to createUser
const userCred = await admin.auth().createUser({
email: email,
password: password,
displayName: name,
});
logger.info(
`registerNewUser: User created successfully with UID: ${userCred.uid}`,
);
await db
.collection('users')
.doc(userCred.uid)
.set({
docId: userCred.uid,
name: name,
phone: phone || null,
email: email,
membership: membership || null,
role: 1, // Default role
isActive: true,
created: FieldValue.serverTimestamp(),
...rest,
});
logger.info(
`registerNewUser: User data stored in Firestore for UID: ${userCred.uid}`,
);
response.send({ success: true, uid: userCred.uid });
} catch (error: unknown) {
const errorMsg = toErrorWithMessage(error).message;
logger.error(
`registerNewUser: Failed during user creation/write: ${errorMsg}`,
{ error },
);
if (
error instanceof Error &&
'code' in error &&
error.code === 'auth/email-already-exists'
) {
response.status(409).send({
error: 'email-already-exists',
message: 'The email address is already in use by another account.',
details: errorMsg,
});
} else {
response.status(500).send({
error: 'internal',
message: 'Failed to register user.',
details: errorMsg,
});
}
}
}); // End of corsMiddlewareHandler callback
}); // End of registerNewUser onRequest
// --- Function 2: visitorRegister (Public Registration) ---
export const visitorRegisterV2 = onRequest(async (request, response) => {
return corsMiddlewareHandler(request, response, async () => {
// Note: Add 'as any' assertion here too if needed for TS build error workaround
// return corsMiddlewareHandler(request as any, response as any, async () => {
logger.info('visitorRegister: Function invoked via CORS Middleware.'); // Log updated
if (request.method !== 'POST') {
logger.warn(`visitorRegister: Method not allowed: ${request.method}`);
response.status(405).send({ message: 'Method Not Allowed.' });
return;
}
const { email, password, name, membership, uid, ...rest } = request.body;
if (!email || !password || !name) {
logger.warn(
'visitorRegister: Missing essential fields (email, password, name).',
);
response.status(400).send({
error: 'missing-fields',
message: 'Missing essential registration fields.',
});
return;
}
try {
logger.info(`visitorRegister: Attempting to create user: ${email}`);
const userCred = await admin.auth().createUser({
email: email,
password: password,
displayName: name,
});
logger.info(`visitorRegister: User created with UID: ${userCred.uid}`);
if (uid && typeof uid === 'string') {
logger.info(
`visitorRegister: Attempting to link phone number from temp UID: ${uid}`,
);
try {
const phoneUser = await admin.auth().getUser(uid);
logger.info(
`visitorRegister: Fetched phone user details for temp UID ${uid}. Phone: ${phoneUser.phoneNumber}`,
);
if (phoneUser.phoneNumber) {
await admin.auth().updateUser(userCred.uid, {
phoneNumber: phoneUser.phoneNumber,
});
logger.info(
`visitorRegister: Phone number ${phoneUser.phoneNumber} linked to new user UID: ${userCred.uid}`,
);
} else {
logger.warn(
`visitorRegister: Temp user UID ${uid} exists but has no phone number.`,
);
}
await admin.auth().deleteUser(uid);
logger.info(
`visitorRegister: Deleted temporary phone auth user UID: ${uid}`,
);
} catch (linkError: unknown) {
const linkErrorMsg = toErrorWithMessage(linkError).message;
logger.error(
`visitorRegister: Error during phone linking/deletion for temp UID ${uid} (non-fatal): ${linkErrorMsg}`,
{ linkError },
);
}
}
logger.info(
`visitorRegister: Storing user data in Firestore for UID: ${userCred.uid}`,
);
await db
.collection('users')
.doc(userCred.uid)
.set({
docId: userCred.uid,
name: name,
email: email,
membership: membership || null,
role: 1,
isActive: true,
created: FieldValue.serverTimestamp(),
...rest,
});
logger.info(
`visitorRegister: Stored user data in Firestore for UID: ${userCred.uid}`,
);
response.send({ success: true, uid: userCred.uid });
} catch (error: unknown) {
const errorMsg = toErrorWithMessage(error).message;
logger.error(
`visitorRegister: Failed during registration for ${email}: ${errorMsg}`,
{ error },
);
if (
error instanceof Error &&
'code' in error &&
error.code === 'auth/email-already-exists'
) {
response.status(409).send({
error: 'email-already-exists',
message: 'The email address is already in use by another account.',
details: errorMsg,
});
} else {
response.status(500).send({
error: 'registration-failed',
message: 'Failed to register the user.',
details: errorMsg,
});
}
}
}); // End of corsMiddlewareHandler callback
}); // End of visitorRegister onRequest