-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from pineylilly/feature/user-routes
Implement auth controller and service
- Loading branch information
Showing
13 changed files
with
294 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
-- CreateEnum | ||
CREATE TYPE "Role" AS ENUM ('user', 'admin'); | ||
|
||
-- CreateTable | ||
CREATE TABLE "User" ( | ||
"id" TEXT NOT NULL, | ||
"email" TEXT NOT NULL, | ||
"password" TEXT NOT NULL, | ||
"displayName" TEXT NOT NULL, | ||
"role" "Role" NOT NULL DEFAULT 'user', | ||
"avatar" TEXT, | ||
|
||
CONSTRAINT "User_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Please do not edit this file manually | ||
# It should be added in your version-control system (i.e. Git) | ||
provider = "postgresql" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// This is your Prisma schema file, | ||
// learn more about it in the docs: https://pris.ly/d/prisma-schema | ||
|
||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? | ||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init | ||
|
||
generator client { | ||
provider = "prisma-client-js" | ||
} | ||
|
||
datasource db { | ||
provider = "postgresql" | ||
url = env("DATABASE_URL") | ||
} | ||
|
||
model User { | ||
id String @id @default(uuid()) | ||
email String @unique | ||
password String | ||
displayName String | ||
role Role @default(user) | ||
avatar String? | ||
createdAt DateTime @default(now()) | ||
updatedAt DateTime @updatedAt @default(now()) | ||
} | ||
|
||
enum Role { | ||
user | ||
admin | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import Elysia from "elysia"; | ||
import { LoginBody, RegisterBody } from "../dto/auth.dto"; | ||
import AuthService from "../services/auth.service"; | ||
import { BadRequestError } from "../utils/error"; | ||
import jwt from "@elysiajs/jwt"; | ||
import { jwtParams } from "../utils/jwtParams"; | ||
|
||
const authService = new AuthService(); | ||
|
||
export const authController = new Elysia({ prefix: '/auth' }) | ||
.use( | ||
jwt({ | ||
name: 'jwt', | ||
secret: process.env.JWT_SECRET! | ||
}) | ||
) | ||
|
||
// POST /auth/register | ||
.post("/register", async ({ jwt, cookie: { auth }, body }) => { | ||
// Register an user | ||
const user = await authService.register(body) | ||
if (!user) { | ||
throw new BadRequestError("Failed to register user") | ||
} | ||
|
||
// Add JWT token to user | ||
auth.set({ | ||
value: await jwt.sign(jwtParams(user)), | ||
httpOnly: true, | ||
maxAge: 7 * 86400, | ||
path: '/', | ||
}) | ||
|
||
return user | ||
}, { | ||
body: RegisterBody | ||
}) | ||
|
||
// POST /auth/login | ||
.post("/login", async ({ jwt, cookie: { auth }, body }) => { | ||
const user = await authService.login(body) | ||
|
||
if (!user) { | ||
throw new BadRequestError("Invalid credentials") | ||
} | ||
|
||
// Add JWT token to user | ||
auth.set({ | ||
value: await jwt.sign(jwtParams(user)), | ||
httpOnly: true, | ||
maxAge: 7 * 86400, | ||
path: '/', | ||
}) | ||
|
||
return user | ||
}, { | ||
body: LoginBody | ||
}) | ||
|
||
// POST /auth/logout | ||
.post("/logout", async ({ cookie: { auth }}) => { | ||
auth.remove() | ||
|
||
return { | ||
message: "Logged out" | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import Elysia from "elysia"; | ||
|
||
export const workspaceController = new Elysia({ prefix: '/users' }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Static, t } from "elysia"; | ||
|
||
export const RegisterBody = t.Object({ | ||
email: t.String({ | ||
format: "email", | ||
minLength: 1, | ||
error: "Email is required", | ||
}), | ||
password: t.String({ | ||
minLength: 8, | ||
error: "Password must be at least 8 characters", | ||
}), | ||
displayName: t.String({ | ||
minLength: 1, | ||
error: "Display name is required", | ||
}), | ||
}) | ||
|
||
export type RegisterInput = Static<typeof RegisterBody> | ||
|
||
export const LoginBody = t.Object({ | ||
email: t.String({ | ||
format: "email", | ||
minLength: 1, | ||
error: "Email is required", | ||
}), | ||
password: t.String({ | ||
minLength: 8, | ||
error: "Password must be at least 8 characters", | ||
}), | ||
}) | ||
|
||
export type LoginInput = Static<typeof LoginBody> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { LoginInput, RegisterInput } from "../dto/auth.dto"; | ||
import bcrypt from "bcrypt"; | ||
import { db } from "../utils/db"; | ||
import { ValidationError } from "elysia"; | ||
import { BadRequestError } from "../utils/error"; | ||
|
||
export default class AuthService { | ||
|
||
async register(data: RegisterInput) { | ||
// Check if email is used | ||
const existedUser = await db.user.findUnique({ | ||
where: { | ||
email: data.email | ||
} | ||
}) | ||
|
||
if (existedUser) { | ||
throw new BadRequestError("Email has been already used"); | ||
} | ||
|
||
// Hash password | ||
const salt = await bcrypt.genSalt(10); | ||
const hashedPassword = await bcrypt.hash(data.password, salt); | ||
|
||
// Create user | ||
const user = await db.user.create({ | ||
data: { | ||
...data, | ||
password: hashedPassword | ||
} | ||
}) | ||
|
||
return user; | ||
} | ||
|
||
|
||
async login(data: LoginInput) { | ||
// Find user by email | ||
const user = await db.user.findUnique({ | ||
where: { | ||
email: data.email | ||
} | ||
}) | ||
|
||
if (!user) { | ||
throw new BadRequestError("Invalid credentials"); | ||
} | ||
|
||
// Compare password | ||
const isValidPassword = await bcrypt.compare(data.password, user.password); | ||
if (!isValidPassword) { | ||
throw new BadRequestError("Invalid credentials"); | ||
} | ||
|
||
return user; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { PrismaClient } from "@prisma/client" | ||
|
||
declare global { | ||
// eslint-disable-next-line no-var | ||
var cachedPrisma: PrismaClient | ||
} | ||
|
||
let prisma: PrismaClient | ||
if (process.env.NODE_ENV === "production") { | ||
prisma = new PrismaClient() | ||
} else { | ||
if (!global.cachedPrisma) { | ||
global.cachedPrisma = new PrismaClient() | ||
} | ||
prisma = global.cachedPrisma | ||
} | ||
|
||
export const db = prisma |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class BadRequestError extends Error { | ||
constructor(public message: string) { | ||
super(message) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { User } from "@prisma/client"; | ||
|
||
export function jwtParams(user: User) { | ||
return { | ||
id: user.id, | ||
email: user.email, | ||
role: user.role | ||
} | ||
} |