224 lines
5.5 KiB
JavaScript
224 lines
5.5 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
|