Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions apps/web/src/components/Auth/Login/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,7 @@ const LoginForm: React.FC<{
return router.generic({ route, query: omit(query, "redirect") });
}, [searchParams]);

const google = useGoogle({
// TODO: this should cover both tutors and students.
// should be implemented once the tutor onboarding is finalized.
role: IUser.Role.Student,
redirect,
});
const google = useGoogle({ redirect });

// ========== manual login ============
const onSuccess = useCallback(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Auth/Register/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Content: React.FC = () => {
}, [params.role]);

useEffect(() => {
if (!role) return navigate(Web.Root);
if (!role) return navigate(Web.Login);
}, [navigate, role]);

return (
Expand Down
4 changes: 1 addition & 3 deletions apps/web/src/components/Auth/Register/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ const RegisterForm: React.FC<{ role?: Role }> = ({ role }) => {
const verifyEmailDialog = useRender();

// ======== Google Registeration ============
const google = useGoogle({
role,
});
const google = useGoogle({ role });

// ========== manual registeration ============
const onSuccess = useCallback(
Expand Down
20 changes: 18 additions & 2 deletions apps/web/src/hooks/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useApi } from "@litespace/headless/api";
import { useUser } from "@litespace/headless/context/user";
import { useFormatMessage } from "@litespace/ui/hooks/intl";
import { useToast } from "@litespace/ui/Toast";
import { safe } from "@litespace/utils/error";
import { ResponseError, safe } from "@litespace/utils/error";
import { IUser } from "@litespace/types";
import { useGoogleLogin, useGoogleOneTapLogin } from "@react-oauth/google";
import { useCallback, useMemo, useState } from "react";
Expand Down Expand Up @@ -46,11 +46,27 @@ export function useGoogle({
api.auth.google({ token, type, role })
);

if (info instanceof Error)
if (info instanceof ResponseError) {
if (info.statusCode === 404) {
return navigate(
router.web({
route: Web.Register,
role: "student",
})
);
}
return toast.error({
title: intl("login.error"),
description: intl(getErrorMessageId(info)),
});
}

if (info instanceof Error) {
return toast.error({
title: intl("login.error"),
description: intl(getErrorMessageId(info)),
});
}

const regularUser = isRegularUser(info.user);
if (info.user && !regularUser) {
Expand Down
39 changes: 21 additions & 18 deletions services/server/src/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import {
forbidden,
noPassword,
notfound,
serviceUnavailable,
wrongPassword,
} from "@/lib/error/api";
import { users } from "@litespace/models";
import { NextFunction, Request, Response } from "express";
import { isSamePassword, registerNewStudent, withImageUrl } from "@/lib/user";
import {
isSamePassword,
registerNewStudent,
registerNewTutor,
withImageUrl,
} from "@/lib/user";
import { IUser } from "@litespace/types";
import { email, password, string } from "@/validation/utils";
import { googleConfig, jwtSecret } from "@/constants";
Expand Down Expand Up @@ -121,22 +125,21 @@ async function loginWithGoogle(
if (user && role && role !== user.role) return next(bad());
if (user && (!role || role === user.role)) return await success(user);

const register = !user;
if (register && !role) return next(bad());
if (register) {
// TODO: remove this condition once the turor onboarding is finalized.
if (role === IUser.Role.Tutor) return next(serviceUnavailable());

const { user } = await registerNewStudent({
email: data.email,
verifiedEmail: data.verified,
role,
});

return await success(user);
}

return next(notfound.user());
const canRegister = !user && role !== undefined;
if (!canRegister) return next(notfound.user());

const { user: registeredUser } =
role === IUser.Role.Student
? await registerNewStudent({
email: data.email,
verifiedEmail: data.verified,
})
: await registerNewTutor({
email: data.email,
verifiedEmail: data.verified,
});

return await success(registeredUser);
}

async function loginWithAuthToken(
Expand Down
7 changes: 2 additions & 5 deletions services/server/src/handlers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,8 @@ export async function create(req: Request, res: Response, next: NextFunction) {
const payload: IUser.CreateApiPayload = createUserPayload.parse(req.body);
const creator = req.user;
const admin = isAdmin(creator);
// both students and tutors can create/register account on the application,
// hover, currently only students can register. (that's temporary)
// TODO: check if its a regular user rather than just a student, once the tutor
// on-boarding is finalized.
if (payload.role !== IUser.Role.Student && !admin) return next(forbidden());
if (!admin && ![IUser.Role.Student, IUser.Role.Tutor].includes(payload.role))
return next(forbidden());

const userObject = await users.findByEmail(payload.email);
if (userObject) return next(exists.user());
Expand Down
25 changes: 23 additions & 2 deletions services/server/src/lib/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import crypto from "node:crypto";
import s3 from "@/lib/s3";
import { isValidPhone } from "@litespace/utils";
import { knex, users, students } from "@litespace/models";
import { IStudent, IUser } from "@litespace/types";
import { knex, users, students, tutors } from "@litespace/models";
import { IStudent, ITutor, IUser } from "@litespace/types";
import { InvalidPhoneNumber, MissingPhoneNumber } from "@/lib/error/local";

export function hashPassword(password: string): string {
Expand Down Expand Up @@ -108,3 +108,24 @@ export async function registerNewStudent(
return { user, student };
});
}

export async function registerNewTutor(
payload: Partial<IUser.CreateApiPayload> &
Partial<ITutor.CreateApiPayload> & { verifiedEmail?: boolean }
) {
return await knex.transaction(async (tx) => {
const user = await users.create(
{
role: IUser.Role.Tutor,
email: payload.email,
password: payload.password ? hashPassword(payload.password) : "",
verifiedEmail: payload.verifiedEmail,
},
tx
);

const tutor = await tutors.create(user.id, tx);

return { user, tutor };
});
}