168 lines
6.1 KiB
JavaScript
168 lines
6.1 KiB
JavaScript
const express = require("express")
|
|
const router = express.Router()
|
|
const bodyParser = require('body-parser')
|
|
var sanitize = require('mongo-sanitize');
|
|
const mongoose = require('mongoose');
|
|
const User = require('./../model/user.js')
|
|
const bcrypt = require('bcrypt')
|
|
const jwt = require('jsonwebtoken')
|
|
require('dotenv').config()
|
|
const JWT_SECRET = process.env.JWT_SECRET
|
|
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
|
|
const speakeasy = require('speakeasy')
|
|
const rateLimit = require('express-rate-limit')
|
|
const limiter = rateLimit({
|
|
windowMs: 5 * 1000, // 5 seconds
|
|
max: 1, // Limit each IP to 1 requests per `window`
|
|
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
|
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
handler: (request, response, next, options) =>{
|
|
return response.json({status: 'error', error: 'Too many requests try again later.'})
|
|
}
|
|
})
|
|
|
|
router.use(bodyParser.json())
|
|
|
|
router.get("/", (req, res) => {
|
|
res.redirect('/')
|
|
})
|
|
|
|
router.get(["/RequestAuth","/RequestAuth.ashx"], (req, res) => {
|
|
if (!req.cookies['.ROBLOSECURITY']){
|
|
res.status(403).end()
|
|
}
|
|
res.send('https://www.mete0r.xyz/Login/Negotiate.ashx?suggest='+req.cookies['.ROBLOSECURITY'])
|
|
}) // studio
|
|
|
|
router.get(["/Negotiate","/Negotiate.ashx"], (req, res) => {
|
|
if (!req.query.suggest){
|
|
res.status(403).end()
|
|
}
|
|
//res.cookie('jwt', req.query.suggest) // maxage is 24 hours
|
|
res.cookie('.ROBLOSECURITY', req.query.suggest)
|
|
res.cookie('.RBXID', req.query.suggest)
|
|
res.send(req.query.suggest)
|
|
}) // studio
|
|
|
|
|
|
router.post("/",limiter,async (req, res) => {
|
|
//console.log(req.headers)
|
|
let {username, password, _2fa} = req.body
|
|
if (!username && req.headers?.["user-agent"]?.includes("RobloxStudio/WinInet") === true){ // Studio login
|
|
username = req.body.cvalue??req.body.username
|
|
password = req.body.password??req.body.ticket
|
|
_2fa = req.body.code
|
|
}
|
|
if (!username || typeof username !== 'string') {
|
|
return res.json({status: 'error', error: 'Usernames needs to be sent and it needs to be a string'})
|
|
}
|
|
if (!password || typeof password !== 'string') {
|
|
return res.json({status: 'error', error: 'Password needs to be sent and it needs to be a string'})
|
|
}
|
|
|
|
if(password.length < 4) {
|
|
return res.json({status: 'error', error: 'Password needs to be at least 5 characters'})
|
|
}
|
|
|
|
sanitizedusername = sanitize(username)
|
|
|
|
const user = await User.findOne({username: sanitizedusername})/*.lean()*/
|
|
if (!user) {
|
|
if (req.headers?.["user-agent"] === "RobloxStudio/WinInet"){ // studio response
|
|
return res.json({
|
|
"errors": [
|
|
{
|
|
"code": 1,
|
|
"message": "Incorrect password"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
return res.json({status: 'error', error: 'Invalid username/password'})
|
|
}
|
|
|
|
if (user.twofasecrets){
|
|
const json = JSON.parse(user.twofasecrets)
|
|
if (json.verified === true){
|
|
if (!_2fa){
|
|
if (req.headers?.["user-agent"] === "RobloxStudio/WinInet"){ // studio response
|
|
return res.json({
|
|
"user": {
|
|
"id": user.userid,
|
|
"name": user.username,
|
|
},
|
|
"twoStepVerificationData": {
|
|
"mediaType": "Email",
|
|
"ticket": password
|
|
},
|
|
"isBanned": false
|
|
})
|
|
}
|
|
return res.json({status: 'error', error: '2FA Enabled on account but 2fa not sent'})
|
|
}
|
|
const valid = speakeasy.totp.verify({
|
|
secret: json.secret,
|
|
encoding: 'ascii',
|
|
token: _2fa
|
|
})
|
|
if (valid === false){
|
|
if (req.headers?.["user-agent"] === "RobloxStudio/WinInet"){ // studio response
|
|
return res.json({
|
|
"errors": [
|
|
{
|
|
"code": 6,
|
|
"message": "Invalid two step verify code."
|
|
}
|
|
]
|
|
})
|
|
}
|
|
return res.json({status: 'error', error: 'Invalid 2FA Code'})
|
|
}
|
|
|
|
}else{
|
|
// basically if they haven't verified that they know the secret before we will just remove it for them
|
|
user.twofasecrets = undefined
|
|
user.markModified('twofasecrets')
|
|
user.save()
|
|
}
|
|
}
|
|
|
|
if(await bcrypt.compare(password, user.password) || password === user.password) {
|
|
// the username and password match
|
|
// lets make a token for them using the data from our database
|
|
const ip = req.headers['cf-connecting-ip'] || req.socket.remoteAddress
|
|
const token = jwt.sign({ id: user._id, username: user.username, admin: user.admin, userid: user.userid, ip, furry: true },JWT_SECRET,{expiresIn: '24h'})
|
|
if (req.headers?.["user-agent"] != "RobloxStudio/WinInet"){
|
|
res.cookie('jwt', token, {SameSite: "Strict",httpOnly: true,maxAge: 24 * 60 * 60 * 1000 }) // maxage is 24 hours
|
|
}
|
|
res.cookie('.ROBLOSECURITY', token, {SameSite: "Strict",httpOnly: true,maxAge: 24 * 60 * 60 * 1000 })
|
|
res.cookie('.RBXID', token, {SameSite: "Strict",httpOnly: true,maxAge: 24 * 60 * 60 * 1000 })
|
|
if (req.url === "/v2/twostepverification/verify"){
|
|
return res.json({})
|
|
}
|
|
if (req.headers?.["user-agent"] === "RobloxStudio/WinInet"){ // studio response
|
|
return res.json({
|
|
"user": {
|
|
"id": user.userid,
|
|
"name": user.username,
|
|
},
|
|
"isBanned": false
|
|
})
|
|
}
|
|
return res.json({status: 'ok', cookie: token})
|
|
}
|
|
if (req.headers?.["user-agent"] === "RobloxStudio/WinInet"){ // studio response
|
|
return res.json({
|
|
"errors": [
|
|
{
|
|
"code": 1,
|
|
"message": "Incorrect password"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
res.status(403).json({status: 'error', error: 'Invalid username/password'})
|
|
})
|
|
|
|
module.exports = router |