1
Fork 0

typescript(multiplayer-backend): initial commit

Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
Matei Adriel 2019-05-19 21:37:39 +03:00 committed by prescientmoon
parent 1544ca69e2
commit 7f3b756378
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
21 changed files with 807 additions and 80 deletions

View file

@ -1,26 +1,56 @@
import * as express from "express"
import { baseUrl, routes } from "../config";
import { performance } from "perf_hooks"
import { shortLogger } from "./routes/logging/shortLog";
import * as sessions from "express-session"
import chalk from "chalk";
export interface serverSetupResults{
time:number;
app:express.Application
import { staticRoutes } from "../config";
import { routes } from "./routes"
import { urlencoded } from "body-parser";
import { database } from "./services/db/firestore"
import { morganChalk } from "./middleware/morgan";
import { sessionMiddleware } from "./middleware/sessions"
// @ts-ignore no declaration file
// import * as store from "firestore-store"
import * as store from "connect-mongo"
import { connection, connected } from "./services/db/mongo";
// const firestore = store(sessions)
export interface serverSetupResults {
app: express.Application
}
export const setupServer = ():serverSetupResults => {
export const setupServer = (): Promise<serverSetupResults> =>
new Promise(async (res, rej) => {
try {
let MongoStore = store(sessions)
const start = performance.now()
const app = express()
await connected
for (let i in routes) {
const route = require(`${process.cwd()}/${baseUrl}${routes[i]}`)
app.use(i, route.router)
}
//create express app
const app = express()
const time = performance.now() - start
const message = `Server created in: ${Math.floor(time)}ms`
shortLogger.log("Server created",message)
app.use(urlencoded({ extended: true }), sessions({
secret: process.env.SESSION_SECRET,
saveUninitialized: false,
resave: false,
store: new MongoStore({ mongooseConnection: connection })
}), morganChalk, sessionMiddleware)
return {app,time}
}
//load static routes
staticRoutes.forEach(route => {
app.use(express.static(`${route}`))
})
//Load normal routes
for (let i in routes) {
app.use(`/${i}`, routes[i])
}
console.log(chalk.bold.green("👏 Succesfully creatd server!"))
res({ app })
}
catch (err) {
rej(err)
}
})

View file

@ -1,11 +1,27 @@
import { config } from "dotenv"
import { setupServer } from "./createServer";
import { connected } from "./services/db/mongo"
import chalk from "chalk";
//extract env variables
//⚓ connect to mongodb
connected.then(val => {
console.log(chalk.bold.green("⚓ Succesfully connected to mongoDb!!!"))
}).catch(err => {
console.log(err)
console.log(chalk.bold.red("😭 Something went wrong when connecting to mongoDb!!!"))
process.exit(1)
})
//🗻 extract env variables
config()
//create the server
const {app} = setupServer()
const main = async () => {
//start listeing to requests
app.listen(process.env.PORT)
//create the server
const { app } = await setupServer()
//start listeing to requests
app.listen(process.env.PORT)
}
main()

View file

@ -1,5 +1,8 @@
import { Request, Response } from "express"
import { whiteList, whiteListUrl } from "../../config"
import { whiteList } from "../../config"
import { includes } from "lodash"
const urls = whiteList.map(urlObject => urlObject.url)
//TODO: remove from here
const defaultMethods = "GET, POST, OPTIONS, PUT, PATCH, DELETE"
@ -9,21 +12,17 @@ const defaultMethods = "GET, POST, OPTIONS, PUT, PATCH, DELETE"
* @param url the url to allow
* @param methods the methods to allow the url to do
*/
export const corsErrorFixer = ({url,methods}:whiteListUrl) => {
return (req: Request, res: Response, next: any) => {
res.setHeader('Access-Control-Allow-Origin', url) // Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Methods', methods || defaultMethods) //// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type') // Request headers you wish to allow
export const corsErrorFixer = (req: Request, res: Response, next: any) => {
const host = req.get("host")
const methods: any = whiteList.find(value => value.url == host) || { methods: undefined }
// Pass to next layer of middleware
next();
}
}
if (!includes(urls, host))
return next()
/**
* used to allow all urls on the whitelist
*/
export const allowWhitelist = () => {
return whiteList.map(value => corsErrorFixer(value))
}
res.setHeader('Access-Control-Allow-Origin', host) // Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Methods', methods.methods || defaultMethods) //// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type') // Request headers you wish to allow
// Pass to next layer of middleware
next();
}

View file

@ -0,0 +1,26 @@
import * as morgan from "morgan"
import chalk from "chalk"
const { floor } = Math
export const morganChalk = morgan(function (tokens, req, res) {
const status = Number(tokens.status(req,res))
const statusFirstDigit = floor(status / 100)
const emoji = (status === 200) ?
"👌" : (status === 203) ?
"🔎" : (status == 202) ?
"🚧" : (status === 302) ?
"🏹" : (statusFirstDigit === 2) ?
"🌝" : (statusFirstDigit === 4) ?
"😠" :
"❓"
return [
chalk.bold(`${emoji} `),
chalk.green.bold(tokens.method(req, res)),
chalk.red.bold(tokens.status(req, res)),
chalk.blue.italic(tokens.url(req, res)),
chalk.yellow(`${tokens['response-time'](req, res)} ms`),
].join(' ');
});

View file

@ -0,0 +1 @@
export * from "./sessions"

View file

@ -0,0 +1,24 @@
import { Response, Request } from "express";
const getToken = (req: Request) => {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { // Authorization: Bearer g1jipjgi1ifjioj
// Handle token presented as a Bearer token in the Authorization header
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
// Handle token presented as URI param
return req.query.token;
} else if (req.cookies && req.cookies.token) {
// Handle token presented as a cookie parameter
return req.cookies.token;
}
// If we return null, we couldn't find a token.
// In this case, the JWT middleware will return a 401 (unauthorized) to the client for this request
return null;
}
export const sessionMiddleware = (req: Request, res: Response, next: Function) => {
const token = getToken(req)
console.log(token)
next()
}

View file

@ -0,0 +1,9 @@
import { createSchema, Type, typedModel, ExtractDoc } from "ts-mongoose"
const PasswordSchema = createSchema({
uid: Type.string(),
password: Type.string()
})
export const Passsord = typedModel("Password", PasswordSchema)
export type PasswordDoc = ExtractDoc<typeof PasswordSchema>;

View file

@ -0,0 +1,13 @@
import { createSchema, Type, typedModel, ExtractDoc } from "ts-mongoose"
const UserSchema = createSchema({
name: Type.string(),
email: Type.string(),
photo: Type.string(),
friends: Type.array().of(Type.string()),
description: Type.optionalString(),
uid: Type.string()
})
export const User = typedModel("User", UserSchema)
export type UserDoc = ExtractDoc<typeof UserSchema>;

View file

@ -1,11 +1,49 @@
import { Router, Response, Request, urlencoded } from "express"
import { Router, Response, Request } from "express"
import { reddirect } from "../../middleware/reddirect";
import { allowWhitelist } from "../../middleware/corsErrorMiddleware";
import * as sessions from "express-session"
import * as validator from "express-validator"
import { generateUid, encodePassword } from "../../services/auth/signup";
import { savePassword } from "../../services/auth/signup";
import { corsErrorFixer } from "../../middleware/corsErrorMiddleware";
import { config } from "dotenv"
import { compare } from "bcryptjs";
import { User, UserDoc } from "../../models/User";
import { Passsord } from "../../models/Password";
//extract env variables
config()
const router = Router()
router.get("/test", (req, res) => {
res.send("just a test")
})
const loginHtml = (req: Request, res: Response) => {
res.send(`
<form action="/auth/login" method=post>
<div>
<label for=email>email</label>
<input type=text id=email name=email>
</div>
<div>
<label for=password>password</label>
<input type=password id=password name=password>
</div>
<div>
<label for=name>name</label>
<input type=name id=name name=name>
</div>
<button type=submit onclick="
alert('click')
fetch('/',{
headers: {
authorization: 'do u see this?'
}
})">Submit</button>
</form>
`)
}
const sayHello = (req: Request, res: Response) => {
res.send(`
<form action="/auth" method=post>
@ -13,34 +51,172 @@ const sayHello = (req: Request, res: Response) => {
<label for=email>email</label>
<input type=text id=email name=email>
</div>
<div>
<label for=password>password</label>
<input type=password id=password name=password>
</div>
<div>
<label for=name>name</label>
<input type=name id=name name=name>
</div>
<button type=submit>Submit</button>
</form>
`)
}
const auth = (req: Request, res: Response, next: any) => {
//validate
req.check("email", "email isnt valid").isEmail()
const errors = req.validationErrors()
const authHandler = async (req: Request, res: Response) => {
try {
//if already logged in return the uid
if (req.session.uid)
return res.redirect("auth/account")
//if we have erros mark it
if (errors)
req.session.errors = errors
//get data from body
const { password, email, name } = req.body
//reddirect to page
res.send(errors || "Succes!!!")
//check if the email isnt used
if (await User.findOne({ name }))
res.redirect("login")
//validate
req.check("email", "email isnt valid").isEmail()
const errors = req.validationErrors()
if (errors)
res.json({ error: `${req.body.email} is not a valid email` })
//generate an uid
const uid = await generateUid()
const user = new User({
email,
friends: [],
name,
photo: process.env.DEFAULTPROFILEPIC,
uid
} as UserDoc) //used for my editor to help me
encodePassword(password)
//save the password and the user in the db
await Promise.all([
encodePassword(password).then(result => savePassword(uid, result)),
user.save()
])
//save things in session
req.session.uid = uid
//save in the session
req.session.save(() => { })
//send uid back
res.json({
succes: true,
data: {
uid
}
}).status(200)
}
catch (errors) {
//send erros to clinet
res.json({ errors })
}
}
router.use(...allowWhitelist())
router.use("*", urlencoded({ extended: true }), validator(), sessions({
secret: process.env.SESSION_SECRET,
saveUninitialized: false,
resave: false
}))
const account = async (req: Request, res: Response) => {
try {
if (!req.session.uid)
res.json({
succes: false,
errors: ["uid doesnt exist"]
}).status(203)
router.get("/", sayHello)
router.post("/", auth)
res.json({
succes: true,
data: {
uid: req.session.uid
}
})
}
catch (errors) {
//send erros to clinet
res.json({ errors })
}
}
const login = async (req: Request, res: Response) => {
try {
//check if we have an email or an username, and if we are note already logged in
if (req.session.uid)
return res.redirect("account")
let type = "name"
if (req.body.email)
type = "email"
const { password } = req.body
const data = req.body[(type == "name") ? "name" : type]
const doc = await User.findOne({ [data]: req.body[data] })
const uid = doc.uid
//check if the password is good
const passwordHash = await Passsord.findOne({ uid })
const match = await compare(password, passwordHash.password)
//return result
if (!match)
return res.json({
succes: false,
errors: ["wrong password"],
data: {
uid,
hash: passwordHash.password,
password,
[data]: req.body[data]
}
}).status(400)
//update the session ond save it
req.session.uid = uid
req.session.save(() => { })
//send it to the clinet
return res.json({ uid })
}
catch (errors) {
return res.json({ errors })
}
}
const logout = async (req: Request, res: Response) => {
//clear the uid
req.session.uid = undefined
//save it to the db
req.session.save(() => { })
res.send({ succes: true })
}
router.use("*", validator(), corsErrorFixer)
router
.get("/", sayHello)
.post("/", authHandler)
.get("/login", loginHtml)
.post("/login", login)
.get("/account", account)
.get("/logout", logout)
.get("/uid", (req, res) => {
res.send(req.session.uid)
})
.get("/test/uid", async (req, res) => {
res.send(await generateUid())
})
router.use("/*", reddirect("/auth"))
export { router }
export const auth = router

View file

@ -0,0 +1 @@
export * from "./auth"

View file

@ -0,0 +1,8 @@
import { auth } from "./auth"
import { logs } from "./logging"
import { Router } from "express";
export const routes:{[key:string]:Router} = {
auth,
logs
}

View file

@ -0,0 +1 @@
export * from "./logs"

View file

@ -1,7 +1,6 @@
import { Router, Response, Request } from "express"
import { shortLogger } from "./shortLog";
import { reddirect } from "../../middleware/reddirect";
import { allowWhitelist } from "../../middleware/corsErrorMiddleware";
const router = Router()
@ -9,8 +8,7 @@ const getShortLogs = (req: Request, res: Response) => {
res.json(shortLogger.logs)
}
router.use(...allowWhitelist())
router.get("/", getShortLogs)
router.use("/*", reddirect("/logs"))
export { router }
export const logs = router

View file

@ -0,0 +1,45 @@
import * as bcrypt from "bcryptjs"
import { promisify } from "util";
import { randomBytes } from "crypto";
import { database } from "../db/firestore";
import { Passsord, PasswordDoc } from "../../models/Password";
import { User } from "../../models/User";
//promisify functions
const hash = promisify(bcrypt.hash)
export async function generateUid(): Promise<string> {
//generate uid
const uid = randomBytes(16).toString("hex")
//check if document exists
const doc = await User.findOne({ uid })
//if theres already an user with the same id, regenerate
if (doc)
return generateUid()
//then return the uid
return uid
}
export function encodePassword(password: string): Promise<string> {
return hash(password, 10)
}
export function savePassword(uid: string, password: string) {
const passwordInstance = new Passsord({
password, uid
} as PasswordDoc)
return passwordInstance.save()
}
export async function getPassword(uid: string) {
//get doc
const doc = await database.doc(`passwords/${uid}`).get()
//return result
return doc.data()
}

View file

@ -0,0 +1,8 @@
import * as admin from "firebase-admin"
export const firebase = admin.initializeApp({
credential: admin.credential.cert('firebase-admin.json'),
databaseURL: 'https://planets-io.firebaseio.com'
});
export const database = firebase.firestore()

View file

@ -0,0 +1 @@
export * from "./mongo"

View file

@ -0,0 +1,16 @@
import { config } from "dotenv"
import { connect, model, connection } from "mongoose"
config()
connect(process.env.DATABASE, { useNewUrlParser: true })
export const connected = new Promise((res, rej) => {
connection.on("open", (...args: any[]) => {
res(...args)
})
connection.on("error", (...args: any[]) => {
rej(...args)
})
})
export { connection }