mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
server/auth: add token authentication
* Users are only authenticated against their password on login, and to retrieve a token * Passwords are wiped from the GUI frontend and cookies after login and token retrieval * Tokens are revoked at the end of the session/logout * If the user chooses the "remember me" option, the token is stored in the cookie * Tokens correctly delete themselves on logout * Tokens can expire at user-specified date * Tokens have their last usage time * Tokens can have user defined descriptions * Users can manage login tokens in their account settings
This commit is contained in:
@ -15,6 +15,7 @@ class Api extends events.EventTarget {
|
||||
this.user = null;
|
||||
this.userName = null;
|
||||
this.userPassword = null;
|
||||
this.token = null;
|
||||
this.cache = {};
|
||||
this.allRanks = [
|
||||
'anonymous',
|
||||
@ -87,11 +88,76 @@ class Api extends events.EventTarget {
|
||||
|
||||
loginFromCookies() {
|
||||
const auth = cookies.getJSON('auth');
|
||||
return auth && auth.user && auth.password ?
|
||||
this.login(auth.user, auth.password, true) :
|
||||
return auth && auth.user && auth.token ?
|
||||
this.loginWithToken(auth.user, auth.token, true) :
|
||||
Promise.resolve();
|
||||
}
|
||||
|
||||
loginWithToken(userName, token, doRemember) {
|
||||
this.cache = {};
|
||||
return new Promise((resolve, reject) => {
|
||||
this.userName = userName;
|
||||
this.token = token;
|
||||
this.get('/user/' + userName + '?bump-login=true')
|
||||
.then(response => {
|
||||
const options = {};
|
||||
if (doRemember) {
|
||||
options.expires = 365;
|
||||
}
|
||||
cookies.set(
|
||||
'auth',
|
||||
{'user': userName, 'token': token},
|
||||
options);
|
||||
this.user = response;
|
||||
resolve();
|
||||
this.dispatchEvent(new CustomEvent('login'));
|
||||
}, error => {
|
||||
reject(error);
|
||||
this.logout();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createToken(userName, options) {
|
||||
let userTokenRequest = {
|
||||
enabled: true,
|
||||
note: 'Web Login Token'
|
||||
};
|
||||
if (typeof options.expires !== 'undefined') {
|
||||
userTokenRequest.expirationTime = new Date().addDays(options.expires).toISOString()
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.post('/user-token/' + userName, userTokenRequest)
|
||||
.then(response => {
|
||||
cookies.set(
|
||||
'auth',
|
||||
{'user': userName, 'token': response.token},
|
||||
options);
|
||||
this.userName = userName;
|
||||
this.token = response.token;
|
||||
this.userPassword = null;
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteToken(userName, userToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.delete('/user-token/' + userName + '/' + userToken, {})
|
||||
.then(response => {
|
||||
const options = {};
|
||||
cookies.set(
|
||||
'auth',
|
||||
{'user': userName, 'token': null},
|
||||
options);
|
||||
resolve();
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
login(userName, userPassword, doRemember) {
|
||||
this.cache = {};
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -103,10 +169,7 @@ class Api extends events.EventTarget {
|
||||
if (doRemember) {
|
||||
options.expires = 365;
|
||||
}
|
||||
cookies.set(
|
||||
'auth',
|
||||
{'user': userName, 'password': userPassword},
|
||||
options);
|
||||
this.createToken(this.userName, options);
|
||||
this.user = response;
|
||||
resolve();
|
||||
this.dispatchEvent(new CustomEvent('login'));
|
||||
@ -118,9 +181,20 @@ class Api extends events.EventTarget {
|
||||
}
|
||||
|
||||
logout() {
|
||||
let self = this;
|
||||
this.deleteToken(this.userName, this.token)
|
||||
.then(response => {
|
||||
self._logout();
|
||||
}, error => {
|
||||
self._logout();
|
||||
});
|
||||
}
|
||||
|
||||
_logout() {
|
||||
this.user = null;
|
||||
this.userName = null;
|
||||
this.userPassword = null;
|
||||
this.token = null;
|
||||
this.dispatchEvent(new CustomEvent('logout'));
|
||||
}
|
||||
|
||||
@ -137,6 +211,10 @@ class Api extends events.EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
isCurrentAuthToken(userToken) {
|
||||
return userToken.token === this.token;
|
||||
}
|
||||
|
||||
_getFullUrl(url) {
|
||||
const fullUrl =
|
||||
(config.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/');
|
||||
@ -258,7 +336,11 @@ class Api extends events.EventTarget {
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.userName && this.userPassword) {
|
||||
if (this.userName && this.token) {
|
||||
req.auth = null;
|
||||
req.set('Authorization', 'Token '
|
||||
+ new Buffer(this.userName + ":" + this.token).toString('base64'))
|
||||
} else if (this.userName && this.userPassword) {
|
||||
req.auth(
|
||||
this.userName,
|
||||
encodeURIComponent(this.userPassword)
|
||||
|
Reference in New Issue
Block a user