Skip to content

Commit

Permalink
feat: improve global login and session speed
Browse files Browse the repository at this point in the history
  • Loading branch information
HugoRCD committed Mar 30, 2024
1 parent e958d88 commit 6836938
Show file tree
Hide file tree
Showing 17 changed files with 76 additions and 45 deletions.
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
<!-- automd:fetch url="gh:hugorcd/shelve/main/apps/cli/README.md" -->

<!-- ⚠️ (fetch) [GET] "https://raw.githubusercontent.com/hugorcd/shelve/main/apps/cli/README.md": 404 Not Found -->

<!-- /automd -->

The Shelve CLI serves as a command-line interface designed for the [Shelve](https://shelve.hrcd.fr/) platform. This tool enables users to authenticate with Shelve, facilitating the seamless transfer of environment variables for project collaboration within a team directly through the terminal interface.

## Installation
Expand Down Expand Up @@ -65,9 +59,14 @@ To start contributing, you can follow these steps:

<!-- /automd -->

<!-- automd:contributors license=MIT author="HugoRCD" -->
<!-- automd:contributors license=Apache author=HugoRCD github="hugorcd/shelve" -->

<!-- ⚠️ (contributors) `github` is required! -->
Published under the [APACHE](https://github.com/HugoRCD/shelve/blob/main/LICENSE) license.
Made by [@HugoRCD](https://github.com/HugoRCD) and [community](https://github.com/HugoRCD/shelve/graphs/contributors) 💛
<br><br>
<a href="https://github.com/HugoRCD/shelve/graphs/contributors">
<img src="https://contrib.rocks/image?repo=HugoRCD/shelve" />
</a>

<!-- /automd -->

Expand All @@ -78,6 +77,3 @@ To start contributing, you can follow these steps:
_🤖 auto updated with [automd](https://automd.unjs.io) (last updated: Sat Mar 30 2024)_

<!-- /automd -->


<!-- /automd -->
8 changes: 6 additions & 2 deletions apps/app/server/api/auth/currentUser.get.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { H3Event } from 'h3'
import { getUserByAuthToken } from '~/server/app/userService'
import { getUserByAuthToken, verifyUserToken } from '~/server/app/userService'

export default eventHandler(async (event: H3Event) => {
const authToken = getCookie(event, 'authToken')
if (!authToken) return null
return await getUserByAuthToken(authToken)
const user = await getUserByAuthToken(authToken)
if (!user) return null
const isTokenValid = await verifyUserToken(authToken, user)
if (!isTokenValid) return null
return user
})
Empty file.
Empty file.
Empty file.
Empty file.
3 changes: 2 additions & 1 deletion apps/app/server/api/user/index.put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { updateUser } from '~/server/app/userService'

export default eventHandler(async (event: H3Event) => {
const user = event.context.user
const authToken = event.context.authToken
const updateUserInput = await readBody(event)
return await updateUser(user, updateUserInput)
return await updateUser(user, updateUserInput, authToken)
})
4 changes: 3 additions & 1 deletion apps/app/server/api/user/session/[id].delete.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { H3Event } from 'h3'
import prisma from '~/server/database/client'
import { removeCachedUserToken } from '~/server/app/userService'

export default eventHandler(async (event: H3Event) => {
const user = event.context.user
const id = getRouterParam(event, 'id') as string
if (!id) throw createError({ statusCode: 400, statusMessage: 'missing params' })
await prisma.session.delete({
const deletedSession = await prisma.session.delete({
where: {
id: parseInt(id),
userId: user.id,
},
})
await removeCachedUserToken(deletedSession.authToken)
return {
statusCode: 200,
message: 'session deleted',
Expand Down
2 changes: 1 addition & 1 deletion apps/app/server/app/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createSession } from '~/server/app/sessionService'
import { getUserByEmail } from '~/server/app/userService'
import prisma from '~/server/database/client'

export async function login(createSessionDto: CreateSessionInput) {
export async function login(createSessionDto: CreateSessionInput): Promise<{ user: User; authToken: string }> {
const user = await getUserByEmail(createSessionDto.email.trim())
if (!user) throw createError({ statusCode: 404, statusMessage: 'user_not_found' })
if (createSessionDto.password && user.password) {
Expand Down
Empty file.
Empty file.
14 changes: 7 additions & 7 deletions apps/app/server/app/projectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,6 @@ async function isProjectAlreadyExists(name: string, userId: number) {
return !!project
}

async function removeCachedUserProjects(userId: string) {
return await useStorage('cache').removeItem(`nitro:functions:getProjectsByUserId:userId:${userId}.json`)
}
async function removeCachedProjectById(id: string) {
return await useStorage('cache').removeItem(`nitro:functions:getProjectById:projectId:${id}.json`)
}

export async function deleteProject(id: string, userId: number) {
await removeCachedUserProjects(userId.toString())
await removeCachedProjectById(id)
Expand All @@ -166,3 +159,10 @@ export async function deleteProject(id: string, userId: number) {
}
})
}

async function removeCachedUserProjects(userId: string) {
return await useStorage('cache').removeItem(`nitro:functions:getProjectsByUserId:userId:${userId}.json`)
}
async function removeCachedProjectById(id: string) {
return await useStorage('cache').removeItem(`nitro:functions:getProjectById:projectId:${id}.json`)
}
12 changes: 7 additions & 5 deletions apps/app/server/app/sessionService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { User, SessionWithCurrent, CreateSessionInput } from '@shelve/types'
import jwt from 'jsonwebtoken'
import prisma from '~/server/database/client'
import { removeCachedUserToken } from '~/server/app/userService'

const runtimeConfig = useRuntimeConfig().private

export async function createSession(user: User, createSessionDto: CreateSessionInput) {
export async function createSession(user: User, createSessionDto: CreateSessionInput): Promise<{ user: User; authToken: string }> {
const authToken = jwt.sign(
{
id: user.id,
Expand Down Expand Up @@ -42,17 +43,18 @@ export async function getSessions(userId: number, authToken: string): Promise<Se
})) as SessionWithCurrent[]
}

export async function deleteSession(authToken: string, userId: number) {
return await prisma.session.delete({
export async function deleteSession(authToken: string, userId: number): Promise<void> {
await removeCachedUserToken(authToken)
await prisma.session.delete({
where: {
authToken,
userId,
},
})
}

export async function deleteSessions(userId: number, authToken: string) {
return await prisma.session.deleteMany({
export async function deleteSessions(userId: number, authToken: string): Promise<void> {
await prisma.session.deleteMany({
where: {
userId,
authToken: {
Expand Down
39 changes: 26 additions & 13 deletions apps/app/server/app/userService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { User, UserCreateInput, UserUpdateInput } from '@shelve/types'
import type { publicUser, User, UserCreateInput, UserUpdateInput } from '@shelve/types'
import { Role } from '@shelve/types'
import jwt from 'jsonwebtoken'
import bcrypt from 'bcryptjs'
Expand Down Expand Up @@ -38,7 +38,7 @@ export async function upsertUser(userCreateInput: UserCreateInput) {
return formatUser(user)
}

export async function getUserByEmail(email: string) {
export async function getUserByEmail(email: string): Promise<User | null> {
return await prisma.user.findUnique({
where: {
email,
Expand All @@ -47,15 +47,15 @@ export async function getUserByEmail(email: string) {
})
}

export async function deleteUser(userId: number) {
return await prisma.user.delete({
export async function deleteUser(userId: number): Promise<void> {
await prisma.user.delete({
where: {
id: userId,
},
})
}

export async function getUserByAuthToken(authToken: string) {
export const getUserByAuthToken = cachedFunction(async (authToken: string): Promise<User> => {
const session = await prisma.session.findFirst({
where: {
authToken,
Expand All @@ -64,19 +64,27 @@ export async function getUserByAuthToken(authToken: string) {
user: true,
},
})
const user = session?.user
if (!user) return null
if (!session)
throw createError({ statusCode: 400, message: 'Invalid token' })
return session.user
}, {
maxAge: 60 * 60,
name: 'getUserByAuthToken',
getKey: (authToken: string) => `authToken:${authToken}`,
})

export async function verifyUserToken(token: string, user: User): Promise<boolean> {
try {
const decoded = jwt.verify(session.authToken, runtimeConfig.authSecret) as jwtPayload
if (decoded.id !== user.id) return null
const decoded = jwt.verify(token, runtimeConfig.authSecret) as jwtPayload
if (decoded.id !== user.id) return false
return true
} catch (error) {
await deleteSession(authToken, user.id)
return null
await deleteSession(token, user.id)
return false
}
return formatUser(user)
}

export async function updateUser(user: User, updateUserInput: UserUpdateInput) {
export async function updateUser(user: User, updateUserInput: UserUpdateInput, authToken: string): Promise<publicUser> {
const newUsername = updateUserInput.username
if (newUsername && newUsername !== user.username) {
const usernameTaken = await prisma.user.findFirst({
Expand All @@ -93,5 +101,10 @@ export async function updateUser(user: User, updateUserInput: UserUpdateInput) {
where: { id: user.id },
data: updateUserInput,
})
await removeCachedUserToken(authToken)
return formatUser(updatedUser)
}

export async function removeCachedUserToken(authToken: string): Promise<void> {
await useStorage('cache').removeItem(`nitro:functions:getUserByAuthToken:authToken:${authToken}.json`)
}
17 changes: 15 additions & 2 deletions apps/app/server/middleware/1.serverAuth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { H3Event } from 'h3'
import { getUserByAuthToken } from '~/server/app/userService'
import { getUserByAuthToken, verifyUserToken } from '~/server/app/userService'

export default defineEventHandler(async (event: H3Event) => {
const protectedRoutes = [
Expand Down Expand Up @@ -27,8 +27,21 @@ export default defineEventHandler(async (event: H3Event) => {
}
event.context.authToken = authToken

const user = event.context.user || (await getUserByAuthToken(authToken))
const user = await getUserByAuthToken(authToken)
if (!user) {
deleteCookie(event, 'authToken')
return sendError(
event,
createError({
statusCode: 401,
statusMessage: 'unauthorized',
}),
)
}

const isTokenValid = await verifyUserToken(authToken, user)
if (!isTokenValid) {
deleteCookie(event, 'authToken')
return sendError(
event,
createError({
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ To start contributing, you can follow these steps:

<!-- /automd -->

<!-- automd:contributors license=Apache author="HugoRCD" -->
<!-- automd:contributors license=Apache author=HugoRCD -->

Published under the [APACHE](https://github.com/HugoRCD/shelve/blob/main/LICENSE) license.
Made by [@HugoRCD](https://github.com/HugoRCD) and [community](https://github.com/HugoRCD/shelve/graphs/contributors) 💛
Expand Down
2 changes: 1 addition & 1 deletion packages/shelve-types/src/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type User = {
password: string | null;
otp: string | null;
avatar: string;
role: Role;
role: Role | string;
createdAt: Date;
updatedAt: Date;
};
Expand Down

0 comments on commit 6836938

Please sign in to comment.