153 lines
4.5 KiB
JavaScript
153 lines
4.5 KiB
JavaScript
|
|
export class LoginIdentity {
|
|
constructor(baseUrl) {
|
|
this._baseUrl = baseUrl;
|
|
this._username = null;
|
|
this._token = null;
|
|
}
|
|
|
|
get authenticated() { return (!!this._token); }
|
|
get username(){ return this._username; }
|
|
get token(){ return this._token; }
|
|
|
|
fetch(url, options={}){
|
|
if (!this._token)
|
|
throw "Unauthorized";
|
|
|
|
if (!options.headers)
|
|
options.headers = {}
|
|
|
|
options.headers["Authorization"] = "Token " + this._token;
|
|
return window.fetch(url, options)
|
|
.then(response => {
|
|
if (response.status === 401){
|
|
this._token = null;
|
|
this._username = null;
|
|
this.saveToken();
|
|
}
|
|
return response;
|
|
});
|
|
}
|
|
|
|
saveToken(){
|
|
let backup = { token: this._token, username: this._username };
|
|
localStorage.setItem("identity", JSON.stringify(backup));
|
|
}
|
|
|
|
restoreToken(){
|
|
let restore = JSON.parse(localStorage.getItem("identity") || '{ "token": null, "username": null }');
|
|
this._token = restore.token;
|
|
this._username = restore.username;
|
|
if (this._token){
|
|
this.fetch(this._baseUrl + "/v1/checkToken")
|
|
.then(response => {
|
|
if (!response.ok)
|
|
{
|
|
this._token = null;
|
|
this._username = null;
|
|
} else {
|
|
this._token = restore.token;
|
|
this._username = restore.username;
|
|
}
|
|
}, (reason)=>{
|
|
console.log(reason);
|
|
this._token = null;
|
|
this._username = null;
|
|
});
|
|
|
|
}
|
|
this._token = null;
|
|
this._username = null;
|
|
}
|
|
|
|
authenticate(username, password) {
|
|
return fetch(this._baseUrl + '/v1/login?username=' + encodeURIComponent(username))
|
|
.then((resp)=>resp.json())
|
|
.then((r)=>this.hashChallenge(password, r.salt, r.challenge))
|
|
.then((r)=>fetch(
|
|
this._baseUrl + '/v1/authenticate?username=' + username + '&response=' + encodeURIComponent(this.encodeBase64(r.response))))
|
|
.then((r)=>{
|
|
if (!r.ok)
|
|
throw "Unauthorized";
|
|
return r.json();
|
|
})
|
|
.then((r)=>{
|
|
this._username = r.username;
|
|
this._token = r.token;
|
|
this.saveToken();
|
|
})
|
|
;
|
|
}
|
|
|
|
decodeBase64(b64){
|
|
let bs = atob(b64);
|
|
let ui8 = new Uint8Array(bs.length);
|
|
for (let i = 0; i < ui8.length; i++)
|
|
ui8[i] = bs.charCodeAt(i);
|
|
return ui8;
|
|
}
|
|
|
|
encodeBase64(bytes){
|
|
const bs = Array.from(bytes, (byte) => String.fromCodePoint(byte), ).join("");
|
|
let b64 = btoa(bs);
|
|
return b64;
|
|
}
|
|
|
|
async hashPassword(password, salt) {
|
|
let bpassword = new TextEncoder().encode(password);
|
|
let bsalt = this.decodeBase64(salt);
|
|
|
|
return this.hash(bsalt, bpassword);
|
|
}
|
|
|
|
async hashChallenge(password, salt, challenge) {
|
|
let hashedPassword = await this.hashPassword(password, salt);
|
|
let bsalt = this.decodeBase64(salt);
|
|
let bchallenge = this.decodeBase64(challenge);
|
|
|
|
console.log("MMM", hashedPassword, bsalt, bchallenge);
|
|
|
|
const data = this.concatTypedArrays(bchallenge, hashedPassword, bchallenge);
|
|
let signatureBuffer = await this.hash(bsalt, data);
|
|
console.log("response", signatureBuffer);
|
|
|
|
return { challenge: challenge, response: signatureBuffer };
|
|
}
|
|
|
|
async hash(key, data){
|
|
const cryptoKey = await window.crypto.subtle.importKey(
|
|
"raw",
|
|
key,
|
|
{name: "HMAC", hash: "SHA-512"},
|
|
false,
|
|
["sign"]
|
|
);
|
|
|
|
// Konvertiere das Byte-Array in ein ArrayBuffer
|
|
const dataBuffer = data.buffer;
|
|
|
|
// Berechne den HMAC-SHA512-Hash
|
|
const signatureBuffer = await window.crypto.subtle.sign(
|
|
"HMAC",
|
|
cryptoKey,
|
|
dataBuffer
|
|
);
|
|
|
|
|
|
return new Uint8Array(signatureBuffer);
|
|
}
|
|
|
|
concatTypedArrays(...typedArrays) {
|
|
let concatLength = 0;
|
|
let offset = 0;
|
|
typedArrays.forEach((v)=>{ concatLength += v.length; });
|
|
let r = new Uint8Array(concatLength);
|
|
typedArrays.forEach((v)=>{
|
|
r.set(v, offset);
|
|
offset += v.length;
|
|
});
|
|
|
|
return r;
|
|
}
|
|
|
|
} |