mirror of
https://github.com/HeyPuter/puter
synced 2024-11-15 06:15:47 +00:00
Merge pull request #397 from HeyPuter/eric/anti-csrf
Update for /logout
This commit is contained in:
commit
9c5849fbce
@ -207,6 +207,9 @@ const install = async ({ services, app }) => {
|
|||||||
|
|
||||||
const { UserProtectedEndpointsService } = require("./services/web/UserProtectedEndpointsService");
|
const { UserProtectedEndpointsService } = require("./services/web/UserProtectedEndpointsService");
|
||||||
services.registerService('__user-protected-endpoints', UserProtectedEndpointsService);
|
services.registerService('__user-protected-endpoints', UserProtectedEndpointsService);
|
||||||
|
|
||||||
|
const { AntiCSRFService } = require('./services/auth/AntiCSRFService');
|
||||||
|
services.registerService('anti-csrf', AntiCSRFService);
|
||||||
}
|
}
|
||||||
|
|
||||||
const install_legacy = async ({ services }) => {
|
const install_legacy = async ({ services }) => {
|
||||||
|
@ -29,6 +29,11 @@ router.post('/logout', auth, express.json(), async (req, res, next)=>{
|
|||||||
// check subdomain
|
// check subdomain
|
||||||
if(require('../helpers').subdomain(req) !== 'api' && require('../helpers').subdomain(req) !== '')
|
if(require('../helpers').subdomain(req) !== 'api' && require('../helpers').subdomain(req) !== '')
|
||||||
next();
|
next();
|
||||||
|
// check anti-csrf token
|
||||||
|
const svc_antiCSRF = req.services.get('anti-csrf');
|
||||||
|
if ( ! svc_antiCSRF.consume_token(req.user.uuid, req.body.anti_csrf) ) {
|
||||||
|
return res.status(400).json({ message: 'incorrect anti-CSRF token' });
|
||||||
|
}
|
||||||
// delete cookie
|
// delete cookie
|
||||||
res.clearCookie(config.cookie_name);
|
res.clearCookie(config.cookie_name);
|
||||||
// delete session
|
// delete session
|
||||||
|
118
packages/backend/src/services/auth/AntiCSRFService.js
Normal file
118
packages/backend/src/services/auth/AntiCSRFService.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
const eggspress = require("../../api/eggspress");
|
||||||
|
const config = require("../../config");
|
||||||
|
const { subdomain } = require("../../helpers");
|
||||||
|
const BaseService = require("../BaseService");
|
||||||
|
|
||||||
|
class CircularQueue {
|
||||||
|
constructor (size) {
|
||||||
|
this.size = size;
|
||||||
|
this.queue = [];
|
||||||
|
this.index = 0;
|
||||||
|
this.map = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
push (item) {
|
||||||
|
if ( this.queue[this.index] ) {
|
||||||
|
this.map.delete(this.queue[this.index]);
|
||||||
|
}
|
||||||
|
this.queue[this.index] = item;
|
||||||
|
this.map.set(item, this.index);
|
||||||
|
this.index = (this.index + 1) % this.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
get (index) {
|
||||||
|
return this.queue[(this.index + index) % this.size];
|
||||||
|
}
|
||||||
|
|
||||||
|
has (item) {
|
||||||
|
return this.map.has(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_consume (item) {
|
||||||
|
if ( this.has(item) ) {
|
||||||
|
const index = this.map.get(item);
|
||||||
|
this.map.delete(item);
|
||||||
|
this.queue[index] = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AntiCSRFService extends BaseService {
|
||||||
|
_construct () {
|
||||||
|
this.map_session_to_tokens = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
['__on_install.routes'] () {
|
||||||
|
const { app } = this.services.get('web-server');
|
||||||
|
|
||||||
|
app.use(eggspress('/get-anticsrf-token', {
|
||||||
|
auth2: true,
|
||||||
|
allowedMethods: ['GET'],
|
||||||
|
}, async (req, res) => {
|
||||||
|
// We disallow `api.` because it has a more relaxed CORS policy
|
||||||
|
const subdomain_check = config.experimental_no_subdomain ||
|
||||||
|
(subdomain(req) !== 'api');
|
||||||
|
if ( ! subdomain_check ) {
|
||||||
|
return res.status(404).send('Hey, stop that!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: session uuid instead of user
|
||||||
|
const token = this.create_token(req.user.uuid);
|
||||||
|
res.send({ token });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
create_token (session) {
|
||||||
|
let tokens = this.map_session_to_tokens[session];
|
||||||
|
if ( ! tokens ) {
|
||||||
|
tokens = new CircularQueue(10);
|
||||||
|
this.map_session_to_tokens[session] = tokens;
|
||||||
|
}
|
||||||
|
const token = this.generate_token_();
|
||||||
|
tokens.push(token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
consume_token (session, token) {
|
||||||
|
const tokens = this.map_session_to_tokens[session];
|
||||||
|
if ( ! tokens ) return false;
|
||||||
|
return tokens.maybe_consume(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_token_ () {
|
||||||
|
return require('crypto').randomBytes(32).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
_test ({ assert }) {
|
||||||
|
// Do this several times, like a user would
|
||||||
|
for ( let i=0 ; i < 30 ; i++ ) {
|
||||||
|
// Generate 30 tokens
|
||||||
|
const tokens = [];
|
||||||
|
for ( let j=0 ; j < 30 ; j++ ) {
|
||||||
|
tokens.push(this.create_token('session'));
|
||||||
|
}
|
||||||
|
// Only the last 10 should be valid
|
||||||
|
const results_for_stale_tokens = [];
|
||||||
|
for ( let j=0 ; j < 20 ; j++ ) {
|
||||||
|
const result = this.consume_token('session', tokens[j]);
|
||||||
|
results_for_stale_tokens.push(result);
|
||||||
|
}
|
||||||
|
assert(() => results_for_stale_tokens.every(v => v === false));
|
||||||
|
// The last 10 should be valid
|
||||||
|
const results_for_valid_tokens = [];
|
||||||
|
for ( let j=20 ; j < 30 ; j++ ) {
|
||||||
|
const result = this.consume_token('session', tokens[j]);
|
||||||
|
results_for_valid_tokens.push(result);
|
||||||
|
}
|
||||||
|
assert(() => results_for_valid_tokens.every(v => v === true));
|
||||||
|
// A completely arbitrary token should not be valid
|
||||||
|
assert(() => this.consume_token('session', 'arbitrary') === false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
AntiCSRFService,
|
||||||
|
};
|
@ -1981,6 +1981,8 @@ window.initgui = async function(){
|
|||||||
|
|
||||||
// logout
|
// logout
|
||||||
try{
|
try{
|
||||||
|
const resp = await fetch(`${window.gui_origin}/get-anticsrf-token`);
|
||||||
|
const { token } = await resp.json();
|
||||||
await $.ajax({
|
await $.ajax({
|
||||||
url: window.gui_origin + "/logout",
|
url: window.gui_origin + "/logout",
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
@ -1989,6 +1991,7 @@ window.initgui = async function(){
|
|||||||
headers: {
|
headers: {
|
||||||
"Authorization": "Bearer " + window.auth_token
|
"Authorization": "Bearer " + window.auth_token
|
||||||
},
|
},
|
||||||
|
data: JSON.stringify({ anti_csrf: token }),
|
||||||
statusCode: {
|
statusCode: {
|
||||||
401: function () {
|
401: function () {
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user