Skip to content

Commit

Permalink
Working user login and protected routes
Browse files Browse the repository at this point in the history
  • Loading branch information
Hajbo committed Sep 27, 2023
1 parent 28379b6 commit 990a1f4
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 28 deletions.
11 changes: 11 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@

import { Elysia } from "elysia";
import { swagger } from "@elysiajs/swagger";
import { jwt } from "@elysiajs/jwt";
import { usersPlugin } from "@/users/users.plugin";
import { AuthenticationError, AuthorizationError } from "@/errors";
import { ALG } from "@/auth";
import { env } from "@/config";

/**
* Add all plugins to the app
*/
Expand Down Expand Up @@ -36,5 +40,12 @@ export const setupApp = () => {
},
})
)
.use(
jwt({
name: "jwt",
secret: env.JWT_SECRET,
alg: ALG,
})
)
.group("/api", (app) => app.use(usersPlugin));
};
37 changes: 36 additions & 1 deletion src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,51 @@
import * as jose from "jose";
import { jwt as jwtPlugin } from "@elysiajs/jwt";
import { User } from "@/users/users.schema";
import { env } from "@/config";
import { AuthenticationError } from "@/errors";
import Elysia from "elysia";

export const ALG = "HS256";

export async function generateToken(user: User) {
const encoder = new TextEncoder();
const secret = encoder.encode(env.JWT_SECRET);

return await new jose.SignJWT({ user: user })
.setProtectedHeader({ alg: "HS256" })
.setProtectedHeader({ alg: ALG })
.setIssuedAt()
.setIssuer("agnyz")
.setAudience(user.email)
.setExpirationTime("24h")
.sign(secret);
}

// TODO: add typing
export const getUserFromHeaders = async ({
jwt,
request: { headers },
}: any) => {
const rawHeader = headers.get("Authorization");
if (!rawHeader) throw new AuthenticationError("Missing authorization header");

const tokenParts = rawHeader?.split(" ");
const tokenType = tokenParts?.[0];
if (tokenType !== "Token")
throw new AuthenticationError(
"Invalid token type. Expected header format: 'Token jwt'"
);

const token = tokenParts?.[1];
const validatedToken = await jwt.verify(token);
if (!validatedToken) throw new AuthenticationError("Invalid token");
return validatedToken;
};

export const requireLogin = async ({ jwt, request }: any) => {
await getUserFromHeaders({ jwt, request });
};

export const getUserEmailFromHeader = async ({ jwt, request }: any) => {
const user = await getUserFromHeaders({ jwt, request });
return user.user.email;
};
56 changes: 41 additions & 15 deletions src/users/users.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,46 @@ import {
UserAuthSchema,
UserLoginSchema,
} from "@/users/users.schema";
import { ALG, getUserEmailFromHeader, requireLogin } from "@/auth";
import { jwt } from "@elysiajs/jwt";
import { env } from "@/config";

export const usersPlugin = new Elysia().use(setupUsers).group("/users", (app) =>
app
.post("", ({ body, store }) => store.usersService.createUser(body.user), {
body: InsertUserSchema,
response: UserAuthSchema,
})
.post(
"/login",
({ body, store }) =>
store.usersService.loginUser(body.user.email, body.user.password),
{
body: UserLoginSchema,
export const usersPlugin = new Elysia()
.use(setupUsers)
.group("/users", (app) =>
app
.post("", ({ body, store }) => store.usersService.createUser(body.user), {
body: InsertUserSchema,
response: UserAuthSchema,
}
)
);
})
.post(
"/login",
({ body, store }) =>
store.usersService.loginUser(body.user.email, body.user.password),
{
body: UserLoginSchema,
response: UserAuthSchema,
}
)
)
.group("/user", (app) =>
app
.use(
jwt({
name: "jwt",
secret: env.JWT_SECRET,
alg: ALG,
})
)
.get(
"",
async ({ jwt, request, store }) =>
store.usersService.findByEmail(
await getUserEmailFromHeader({ jwt, request })
),
{
beforeHandle: requireLogin,
response: UserAuthSchema,
}
)
);
24 changes: 12 additions & 12 deletions src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,25 @@
// in charge of business logic - generate slug, fetch data from other services, cache something, etc.
import { NotFoundError } from "elysia";
import { UsersRepository } from "@/users/users.repository";
import { UserToCreate } from "@/users/users.schema";
import { User, UserToCreate } from "@/users/users.schema";
import { generateToken } from "@/auth";
import { AuthenticationError } from "@/errors";

export class UsersService {
constructor(private readonly repository: UsersRepository) {}

async findAll() {
return this.repository.findAll();
async findByEmail(email: string) {
const user = await this.repository.findByEmail(email);
if (!user) {
throw new NotFoundError("User not found");
}
return await this.generateUserResponse(user);
}

async createUser(user: UserToCreate) {
user.password = await Bun.password.hash(user.password);
const newUser = await this.repository.createUser(user);
return {
user: {
email: newUser.email,
bio: newUser.bio,
image: newUser.image,
username: newUser.username,
token: await generateToken(newUser),
},
};
return await this.generateUserResponse(newUser);
}

async loginUser(email: string, password: string) {
Expand All @@ -35,6 +31,10 @@ export class UsersService {
if (!(await Bun.password.verify(password, user.password))) {
throw new AuthenticationError("Invalid password");
}
return await this.generateUserResponse(user);
}

private async generateUserResponse(user: User) {
return {
user: {
email: user.email,
Expand Down

0 comments on commit 990a1f4

Please sign in to comment.