add simple app authorization page
This commit is contained in:
parent
abc03b4cde
commit
83941add4d
21
css/authorize.css
Normal file
21
css/authorize.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
button {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
width: 50%;
|
||||||
|
border: 3px solid green;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
26
package-lock.json
generated
26
package-lock.json
generated
@ -17,6 +17,7 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-fileupload": "^1.3.1",
|
"express-fileupload": "^1.3.1",
|
||||||
"express-useragent": "^1.0.15",
|
"express-useragent": "^1.0.15",
|
||||||
|
"hcaptcha": "^0.1.1",
|
||||||
"html-minifier-terser": "^7.1.0",
|
"html-minifier-terser": "^7.1.0",
|
||||||
"lru-cache": "^7.14.1",
|
"lru-cache": "^7.14.1",
|
||||||
"mysql2": "^3.1.0",
|
"mysql2": "^3.1.0",
|
||||||
@ -26,6 +27,9 @@
|
|||||||
"uglify-js": "^3.17.4",
|
"uglify-js": "^3.17.4",
|
||||||
"unsafe_encrypt": "^1.0.4",
|
"unsafe_encrypt": "^1.0.4",
|
||||||
"ws": "^8.12.0"
|
"ws": "^8.12.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@hcaptcha/types": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@colors/colors": {
|
"node_modules/@colors/colors": {
|
||||||
@ -80,6 +84,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hcaptcha/types": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
@ -1086,6 +1096,11 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hcaptcha": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hcaptcha/-/hcaptcha-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-iMrDmH2VpIEKOrcKWidVjI89FdDKTEdZ7PfPWkP27sTazIIkob8YfdY2ezaufAnWBiUUcvzsn0qF+dyXtBH2Vw=="
|
||||||
|
},
|
||||||
"node_modules/hpack.js": {
|
"node_modules/hpack.js": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
||||||
@ -2498,6 +2513,12 @@
|
|||||||
"yargs": "^16.2.0"
|
"yargs": "^16.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@hcaptcha/types": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hcaptcha/types/-/types-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-1mbU6eSGawRrqeahRrOzZo/SVLI6oZ5/azuBpSyVrRRR96CnS3fOVDWfzxpngfxKD0/I9Rwu6c/3ITqD8rXeTQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@jridgewell/gen-mapping": {
|
"@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
|
||||||
@ -3278,6 +3299,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||||
},
|
},
|
||||||
|
"hcaptcha": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hcaptcha/-/hcaptcha-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-iMrDmH2VpIEKOrcKWidVjI89FdDKTEdZ7PfPWkP27sTazIIkob8YfdY2ezaufAnWBiUUcvzsn0qF+dyXtBH2Vw=="
|
||||||
|
},
|
||||||
"hpack.js": {
|
"hpack.js": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-fileupload": "^1.3.1",
|
"express-fileupload": "^1.3.1",
|
||||||
"express-useragent": "^1.0.15",
|
"express-useragent": "^1.0.15",
|
||||||
|
"hcaptcha": "^0.1.1",
|
||||||
"html-minifier-terser": "^7.1.0",
|
"html-minifier-terser": "^7.1.0",
|
||||||
"lru-cache": "^7.14.1",
|
"lru-cache": "^7.14.1",
|
||||||
"mysql2": "^3.1.0",
|
"mysql2": "^3.1.0",
|
||||||
@ -36,5 +37,8 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/002Hub/IPost/issues"
|
"url": "https://github.com/002Hub/IPost/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/002Hub/IPost#readme"
|
"homepage": "https://github.com/002Hub/IPost#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@hcaptcha/types": "^1.0.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export const setup = function (router, con, server) {
|
|||||||
console.log("error parsing header",err)
|
console.log("error parsing header",err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(req.body.auth != undefined) {
|
if(req.body.auth !== undefined && req.originalUrl!=="/redeemauthcode") {
|
||||||
if(typeof req.body.auth === "string") {
|
if(typeof req.body.auth === "string") {
|
||||||
try{
|
try{
|
||||||
req.body.auth = JSON.parse(req.body.auth)
|
req.body.auth = JSON.parse(req.body.auth)
|
||||||
|
125
routes/authorize.js
Normal file
125
routes/authorize.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import {randomBytes} from "crypto"
|
||||||
|
import {SHA256} from "../extra_modules/SHA.js";
|
||||||
|
import {unsign} from "../extra_modules/unsign.js";
|
||||||
|
|
||||||
|
export const setup = function (router, con, server) {
|
||||||
|
const temp_code_to_token = {}
|
||||||
|
router.post("/authorize",async (req,res) => {
|
||||||
|
if (!unsign(req.cookies.AUTH_COOKIE, req, res)){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = await server.hcaptcha.verify(req.body["h-captcha-response"])
|
||||||
|
|
||||||
|
if(data.success) {
|
||||||
|
|
||||||
|
console.log("captcha success")
|
||||||
|
|
||||||
|
let appid = req.body.application_id
|
||||||
|
if(typeof appid === "string") {
|
||||||
|
appid = Number(appid)
|
||||||
|
}
|
||||||
|
if(typeof appid === "number") {
|
||||||
|
|
||||||
|
const token = randomBytes(150).toString("base64")
|
||||||
|
|
||||||
|
let tokencode;
|
||||||
|
while(tokencode===undefined || temp_code_to_token[tokencode]!==undefined) {
|
||||||
|
tokencode = randomBytes(15).toString("base64")
|
||||||
|
}
|
||||||
|
temp_code_to_token[tokencode]={
|
||||||
|
"userid":res.locals.userid,
|
||||||
|
"appid":appid,
|
||||||
|
"token":token
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
let data = temp_code_to_token[tokencode]
|
||||||
|
if(data !== undefined && data.token===token && data.appid === appid && data.userid === userid) {
|
||||||
|
temp_code_to_token[tokencode]=undefined
|
||||||
|
}
|
||||||
|
}, 300000); //wait for 5 minutes
|
||||||
|
|
||||||
|
const sql = "SELECT application_auth_url FROM ipost.application where application_id=?"
|
||||||
|
|
||||||
|
con.query(sql,[appid],(err,result) => {
|
||||||
|
if(err || result.length !== 1) {
|
||||||
|
console.err(err)
|
||||||
|
res.redirect(`/authorize?id=${req.body.application_id}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.redirect(`${result[0].application_auth_url}?code=${tokencode}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect(`/authorize?id=${req.body.application_id}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.post("/redeemauthcode", (req,res) => {
|
||||||
|
|
||||||
|
if(temp_code_to_token[req.body.authcode]===undefined) {
|
||||||
|
res.status(400)
|
||||||
|
res.json({"status":400,"message":"invalid code given"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof req.body.auth === "string") {
|
||||||
|
try{
|
||||||
|
req.body.auth = JSON.parse(req.body.auth)
|
||||||
|
} catch(err) {
|
||||||
|
console.log("error parsing",err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(
|
||||||
|
typeof req.body.auth !== "object" ||
|
||||||
|
typeof req.body.auth.secret !== "string" ||
|
||||||
|
typeof req.body.auth.appid !== "number" ||
|
||||||
|
req.body.auth.secret.length !== 200 ||
|
||||||
|
Buffer.from(req.body.auth.secret,"base64").length !== 150 ||
|
||||||
|
req.body.auth.appid !== temp_code_to_token[req.body.authcode].appid
|
||||||
|
) {
|
||||||
|
//console.log(1,req.body.auth,temp_code_to_token[req.body.authcode].appid)
|
||||||
|
res.status(420).send("invalid authentication object")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appid = req.body.auth.appid
|
||||||
|
|
||||||
|
const checksecret = SHA256(req.body.auth.secret,appid,10000)
|
||||||
|
|
||||||
|
const checksql = "SELECT application_id from ipost.application where application_secret=? and application_id=?"
|
||||||
|
const checkvalues = [checksecret,appid]
|
||||||
|
|
||||||
|
con.query(checksql,checkvalues,(error,result_object) => {
|
||||||
|
|
||||||
|
if(error || result_object[0]===undefined || result_object[0].application_id!==appid) {
|
||||||
|
res.status(400)
|
||||||
|
res.json({"status":400,"message":"invalid code given"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = temp_code_to_token[req.body.authcode]
|
||||||
|
temp_code_to_token[req.body.authcode] = undefined
|
||||||
|
|
||||||
|
|
||||||
|
const sql = "INSERT INTO `ipost`.`auth_tokens`(`auth_token`,`auth_token_u_id`,`auth_token_isfrom_application_id`) VALUES(?,?,?);"
|
||||||
|
|
||||||
|
const values = [SHA256(data.token,appid,10000),data.userid,data.appid] //token,id,appid
|
||||||
|
con.query(sql,values,(err,result) => {
|
||||||
|
if(err) {
|
||||||
|
res.json({"status":500,"message":"error redeeming code"})
|
||||||
|
console.err(err)
|
||||||
|
} else {
|
||||||
|
res.json({"status":200,"message":"successfully redeemed code","token":data.token})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
@ -11,6 +11,7 @@ import { setup as userroutessetup } from "./api/userRoutes.js";
|
|||||||
import { setup as servefilessetup} from "./serve_static_files.js"
|
import { setup as servefilessetup} from "./serve_static_files.js"
|
||||||
import { setup as userfilessetup} from "./userfiles.js"
|
import { setup as userfilessetup} from "./userfiles.js"
|
||||||
import { setup as userauthsetup} from "./user_auth.js"
|
import { setup as userauthsetup} from "./user_auth.js"
|
||||||
|
import { setup as applicationsetup} from "./authorize.js"
|
||||||
|
|
||||||
export const setup = function (router, con, server) {
|
export const setup = function (router, con, server) {
|
||||||
const setuproute = handler => handler(router,con,server)
|
const setuproute = handler => handler(router,con,server)
|
||||||
@ -34,4 +35,6 @@ export const setup = function (router, con, server) {
|
|||||||
setuproute(userfilessetup) //needs getPID and getDMPID
|
setuproute(userfilessetup) //needs getPID and getDMPID
|
||||||
|
|
||||||
setuproute(userauthsetup) //login & register
|
setuproute(userauthsetup) //login & register
|
||||||
|
|
||||||
|
setuproute(applicationsetup)
|
||||||
}
|
}
|
@ -80,7 +80,8 @@ export const setup = function (router, con, server) {
|
|||||||
newrelic: load_var("./extra_modules/newrelic_monitor.html"),
|
newrelic: load_var("./extra_modules/newrelic_monitor.html"),
|
||||||
getPID: server.global_page_variables.getPID,
|
getPID: server.global_page_variables.getPID,
|
||||||
getDMPID: server.global_page_variables.getDMPID,
|
getDMPID: server.global_page_variables.getDMPID,
|
||||||
unauthorized_description: "Chat now by creating an account on IPost"
|
unauthorized_description: "Chat now by creating an account on IPost",
|
||||||
|
hcaptcha_sitekey: server.hcaptcha.sitekey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
16
server.js
16
server.js
@ -13,6 +13,7 @@ import {unsign} from "./extra_modules/unsign.js";
|
|||||||
import { readFileSync, appendFile } from "fs";
|
import { readFileSync, appendFile } from "fs";
|
||||||
import { format } from "util";
|
import { format } from "util";
|
||||||
import { setup as SETUP_ROUTES} from "./routes/setup_all_routes.js"
|
import { setup as SETUP_ROUTES} from "./routes/setup_all_routes.js"
|
||||||
|
import { verify as verifyHCaptcha_int } from "hcaptcha"
|
||||||
|
|
||||||
import { ensureExists } from "./extra_modules/ensureExists.js"
|
import { ensureExists } from "./extra_modules/ensureExists.js"
|
||||||
|
|
||||||
@ -58,6 +59,13 @@ function log_info(level, ...info) {
|
|||||||
}
|
}
|
||||||
console.log = log_info;
|
console.log = log_info;
|
||||||
|
|
||||||
|
|
||||||
|
const hcaptcha_secret = config.hcaptcha_secret
|
||||||
|
// wrapper for the HCaptcha verify function
|
||||||
|
function verifyHCaptcha(token) {
|
||||||
|
return verifyHCaptcha_int(hcaptcha_secret,token,undefined,config.hcaptcha_sitekey)
|
||||||
|
}
|
||||||
|
|
||||||
const WebSocket = ws.WebSocketServer;
|
const WebSocket = ws.WebSocketServer;
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@ -259,7 +267,7 @@ app.use(bodyParser.default.json({ limit: "100mb" }));
|
|||||||
app.use(bodyParser.default.urlencoded({ limit: "100mb", extended: true }));
|
app.use(bodyParser.default.urlencoded({ limit: "100mb", extended: true }));
|
||||||
app.use(cookieParser(cookiesecret));
|
app.use(cookieParser(cookiesecret));
|
||||||
app.use(compression())
|
app.use(compression())
|
||||||
var blocked_headers = [
|
let blocked_headers = [
|
||||||
'HTTP_VIA',
|
'HTTP_VIA',
|
||||||
'HTTP_X_FORWARDED_FOR',
|
'HTTP_X_FORWARDED_FOR',
|
||||||
'HTTP_FORWARDED_FOR',
|
'HTTP_FORWARDED_FOR',
|
||||||
@ -334,7 +342,11 @@ var commonfunctions = {
|
|||||||
ensureExists,
|
ensureExists,
|
||||||
"dirname": __dirname,
|
"dirname": __dirname,
|
||||||
config,
|
config,
|
||||||
DID_I_FINALLY_ADD_HTTPS
|
DID_I_FINALLY_ADD_HTTPS,
|
||||||
|
hcaptcha: {
|
||||||
|
"verify":verifyHCaptcha,
|
||||||
|
"sitekey":config.hcaptcha_sitekey
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SETUP_ROUTES(router,con,commonfunctions)
|
SETUP_ROUTES(router,con,commonfunctions)
|
||||||
|
@ -155,12 +155,14 @@
|
|||||||
"level": 5
|
"level": 5
|
||||||
},
|
},
|
||||||
"ssl": {
|
"ssl": {
|
||||||
"privateKey": "/etc/letsencrypt/live/ipost.rocks/privkey.pem",
|
"privateKey": "./etc/letsencrypt/live/ipost.rocks/privkey.pem",
|
||||||
"certificate" : "/etc/letsencrypt/live/ipost.rocks/cert.pem"
|
"certificate" : "./etc/letsencrypt/live/ipost.rocks/fullchain.pem"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"http": 9999,
|
"http": 9999,
|
||||||
"https": 9998
|
"https": 9998
|
||||||
},
|
},
|
||||||
"disallow_proxies_by_headers": true
|
"disallow_proxies_by_headers": true,
|
||||||
|
"hcaptcha_secret": "0x0000000000000000000000000000000000000000",
|
||||||
|
"hcaptcha_sitekey": "10000000-ffff-ffff-ffff-000000000001"
|
||||||
}
|
}
|
||||||
|
31
views/authorize.html
Normal file
31
views/authorize.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<% if(user.username === undefined) { %>
|
||||||
|
<meta name="description" content="<%-unauthorized_description%>">
|
||||||
|
<% } else { %>
|
||||||
|
<meta name="description" content="IPost Extension Authorization Page">
|
||||||
|
<% } %>
|
||||||
|
<title>Authorize App</title>
|
||||||
|
<style>
|
||||||
|
<%- globalcss %>
|
||||||
|
<%- loadfile("./css/authorize.css") %>
|
||||||
|
</style>
|
||||||
|
<% if(user.username === undefined) { %>
|
||||||
|
<script> document.location.href = '/no_login?r='+encodeURIComponent(document.location.pathname) </script>
|
||||||
|
<% } else { %>
|
||||||
|
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||||
|
<% } %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="center">
|
||||||
|
<h1>Authorize App</h1>
|
||||||
|
<p>Please authorize the app to access your information:</p>
|
||||||
|
<form action="/authorize" method="post">
|
||||||
|
<input type="number" value=<%- query.id %> class="hidden" name="application_id" id="application_id">
|
||||||
|
<div class="h-captcha" data-sitekey="<%- hcaptcha_sitekey %>"></div>
|
||||||
|
<input type="submit" value="Authorize">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user