feat(auth): add registration feature (wip)@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1663228194515,
+ "_generatedAtUnix": 1663360458599,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
@@ -26,7 +26,7 @@
"pathSource": "./app/views/InternalErrorView.tsx"
},
"RegisterView": {
- "hash": "b56f4d1bde44d5f2ecea87c3a91467032b5cc1de",
+ "hash": "43766073188abc2654160639989e88f1a6d9e0e0",
"pathSource": "./app/views/auth/RegisterView.tsx"
}
}
@@ -3,17 +3,59 @@ import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
import { AppRoute, AppRoutesParams } from "../../routes";
import RegisterView, { RegisterViewProps } from "../../views/auth/RegisterView";
+import { makeAuthService } from "../../services/auth";
-const postRegisterView: ReqHandler = (request, reply) => {
- const { username, password } =
- request.body as AppRoutesParams[AppRoute.AUTH_REGISTER_ACTION]["body"];
+const postRegisterView: ReqHandler = async (request, reply) => {
+ const authService = makeAuthService({ request });
+ const reqHandler = reply.makeRequestHandler(request, reply);
- password;
+ const {
+ email_address: emailAddress,
+ username,
+ password,
+ } = request.body as AppRoutesParams[AppRoute.AUTH_REGISTER_ACTION]["body"];
- const reqHandler = reply.makeRequestHandler(request, reply);
- return reqHandler<RegisterViewProps>(RegisterView.name, {
- initialValues: { username },
+ const initialValues = { emailAddress, username };
+
+ if (request.validationError != null) {
+ const {
+ message: errorMessage,
+ validation,
+ validationContext,
+ } = request.validationError;
+
+ console.log("validation:", validation);
+ console.log("validationContext:", validationContext);
+
+ return reqHandler<RegisterViewProps>(RegisterView.name, {
+ errorMessage,
+ initialValues,
+ });
+ }
+
+ if (await authService.isExistingUsername(username)) {
+ return reqHandler<RegisterViewProps>(RegisterView.name, {
+ errorMessage: "This is username is already taken. Please choose another.",
+ initialValues: { emailAddress, username },
+ });
+ }
+
+ const newUser = await authService.createUser({
+ emailAddress,
+ username,
+ password,
});
+
+ console.log(`Made new user with id: ${newUser.id}`);
+
+ request.session.data.authenticated = true;
+ request.session.data.curr_user_uid = newUser.id;
+ request.session.data.curr_user_role = newUser.role;
+ request.session.data.curr_user_username = newUser.username;
+ request.session.data.curr_user_avatar_uri = newUser.avatarUri;
+
+ reply.redirect(request.namedViewsPathMap[AppRoute.HOME]);
+ return reply;
};
export default postRegisterView;
@@ -25,6 +25,7 @@ export interface AppRoutesParams extends IRouteParams {
[AppRoute.AUTH_REGISTER]: undefined;
[AppRoute.AUTH_REGISTER_ACTION]: {
body: {
+ email_address: string;
username: string;
password: string;
};
@@ -51,9 +52,10 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
[AppRoute.AUTH_REGISTER_ACTION]: {
body: {
type: "object",
- required: ["username", "password"],
+ required: ["email_address", "username", "password"],
additionalProperties: false,
properties: {
+ email_address: { type: "string" },
username: { type: "string" },
password: { type: "string" },
},
@@ -148,7 +148,7 @@ async function main(): Promise<AppServer> {
cookieOptions: cookiesOpts,
getUniqId: cuid as () => string,
password: Env.COOKIE_SECRET,
- storeAdapter: new PrismaSessionAdapter(prisma),
+ storeAdapter: new PrismaSessionAdapter(prisma) as any,
ttl: Const.SESSION_TTL_SECONDS,
initialSession: {
sessionId: null,
@@ -0,0 +1,27 @@
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import { User } from "@prisma/client";
+// app
+import type { AuthServiceCreateUserDTO, AuthServiceDeps } from "./types";
+
+const makeCreateUser: ServiceMethodFactory<
+ AuthServiceDeps,
+ [AuthServiceCreateUserDTO],
+ Promise<User>
+> = ({ request }) => {
+ const hashPassword = (x: string) => x;
+ return async ({ emailAddress, username, password }) => {
+ const user = await request.prisma.user.create({
+ data: {
+ email: emailAddress,
+ username: username,
+ hashedPassword: hashPassword(password),
+ },
+ });
+
+ return user;
+ };
+};
+
+export default makeCreateUser;
@@ -4,10 +4,12 @@ import { makeService } from "@ethicdevs/react-monolith";
import type { AuthServiceAPI, AuthServiceDeps } from "./types";
// service methods
import { default as makeComparePasswordHashes } from "./comparePasswordHashes";
+import { default as makeCreateUser } from "./createUser";
import { default as makeIsExistingUsername } from "./isExistingUsername";
export const makeAuthService = makeService<AuthServiceAPI, AuthServiceDeps>({
comparePasswordHashes: makeComparePasswordHashes,
+ createUser: makeCreateUser,
isExistingUsername: makeIsExistingUsername,
isExistingUserUid: () => () => undefined,
findUserByUid: () => () => undefined,
@@ -6,10 +6,18 @@ import type { AuthServiceDeps } from "./types";
const makeIsExistingUsername: ServiceMethodFactory<
AuthServiceDeps,
[string],
- boolean
-> = (_) => {
- return (username) => {
- return username === "username";
+ Promise<boolean>
+> = ({ request }) => {
+ return async (username) => {
+ const matchingUser = await request.prisma.user.findUnique({
+ select: {
+ id: true,
+ },
+ where: {
+ username,
+ },
+ });
+ return matchingUser != null;
};
};
@@ -3,10 +3,19 @@ import {
ServiceApiContract,
ServiceDependencies,
} from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import { User } from "@prisma/client";
+
+export interface AuthServiceCreateUserDTO {
+ emailAddress: string;
+ username: string;
+ password: string;
+}
export interface AuthServiceAPI extends ServiceApiContract {
compareUserPasswordHashes(a: string, b: string): boolean;
- isExistingUsername(username: string): boolean;
+ createUser(dto: AuthServiceCreateUserDTO): Promise<User>;
+ isExistingUsername(username: string): Promise<boolean>; // implemented.
isExistingUserUid(userUid: string): boolean;
findUserByUsername(username: string): void;
findUserByUid(userUid: string): void;
@@ -19,15 +19,27 @@ const RegisterView: ReactView<RegisterViewProps> = ({
<Layout {...commonProps} showSideMenu={false}>
<PageWrapper>
<form action={`/auth/register`} method={"POST"}>
+ {/* Email Address */}
+ <div>
+ <label htmlFor={"username"}>Email Address:</label>
+ <input
+ type={"text"}
+ name={"email_address"}
+ placeholder={"i.e. john.doe@provider.tld..."}
+ defaultValue={initialValues?.username}
+ />
+ </div>
+ {/* Username */}
<div>
<label htmlFor={"username"}>Username:</label>
<input
type={"text"}
name={"username"}
- placeholder={"Choose a username (i.e. john.doe)..."}
+ placeholder={"i.e. john.doe, jane.smith, etc..."}
defaultValue={initialValues?.username}
/>
</div>
+ {/* Password */}
<div>
<label htmlFor={"username"}>Password:</label>
<input
@@ -37,6 +49,7 @@ const RegisterView: ReactView<RegisterViewProps> = ({
defaultValue={initialValues?.password}
/>
</div>
+ {/* Submit Button */}
<Button type={"submit"}>Create my Account</Button>
</form>
</PageWrapper>
@@ -1,6 +1,13 @@
+// 3rd-party
import fastify from "fastify";
-
-import type { AppThemeScheme, SectionsWithPages } from "../../app/types";
+// generated via script[generate:prisma]
+import { PrismaClient } from "@prisma/client";
+// app
+import type {
+ AppSessionData,
+ AppThemeScheme,
+ SectionsWithPages,
+} from "../../app/types";
declare module "@ethicdevs/fastify-custom-session" {
declare interface CustomSession extends AppSessionData {}