use salt + md5 hash mode instead of plain text password

This commit is contained in:
Thomas Amland 2020-07-27 19:15:46 +02:00
parent 9feae7ed44
commit ec42ea82eb
7 changed files with 46 additions and 14 deletions

View File

@ -13,6 +13,7 @@
"bootstrap-vue": "^2.15.0", "bootstrap-vue": "^2.15.0",
"howler": "^2.2.0", "howler": "^2.2.0",
"material-design-icons-iconfont": "^5.0.1", "material-design-icons-iconfont": "^5.0.1",
"md5-es": "1.8.2",
"roboto-fontface": "*", "roboto-fontface": "*",
"uuid": "^8.2.0", "uuid": "^8.2.0",
"vue": "^2.6.10", "vue": "^2.6.10",

View File

@ -49,7 +49,6 @@ export default Vue.extend({
async created() { async created() {
this.server = await this.$auth.server; this.server = await this.$auth.server;
this.username = await this.$auth.username; this.username = await this.$auth.username;
this.password = await this.$auth.password;
const success = await this.$auth.autoLogin(); const success = await this.$auth.autoLogin();
if (success) { if (success) {
this.$store.commit("setLoginSuccess", { username: this.username}); this.$store.commit("setLoginSuccess", { username: this.username});
@ -61,7 +60,7 @@ export default Vue.extend({
methods: { methods: {
login() { login() {
this.busy = true; this.busy = true;
this.$auth.login(this.server, this.username, this.password, this.rememberLogin) this.$auth.loginWithPassword(this.server, this.username, this.password, this.rememberLogin)
.then(() => { .then(() => {
this.$store.commit("setLoginSuccess", { username: this.username }); this.$store.commit("setLoginSuccess", { username: this.username });
this.$router.push(this.returnTo); this.$router.push(this.returnTo);

View File

@ -1,35 +1,45 @@
import axios from 'axios'; import axios from 'axios';
import { randomString, md5 } from '@/shared/utils';
export class AuthService { export class AuthService {
public server: string = ""; public server: string = "";
public username: string = ""; public username: string = "";
public password: string = ""; public salt: string = "";
public hash: string = "";
private authenticated: boolean = false; private authenticated: boolean = false;
constructor() { constructor() {
this.server = localStorage.getItem("server") || "/api"; this.server = localStorage.getItem("server") || "/api";
this.username = localStorage.getItem("username") || "guest1"; this.username = localStorage.getItem("username") || "guest1";
this.password = localStorage.getItem("password") || ""; this.salt = localStorage.getItem("salt") || "";
this.hash = localStorage.getItem("hash") || "";
} }
private saveSession() { private saveSession() {
localStorage.setItem("server", this.server); localStorage.setItem("server", this.server);
localStorage.setItem("username", this.username); localStorage.setItem("username", this.username);
localStorage.setItem("password", this.password); localStorage.setItem("salt", this.salt);
localStorage.setItem("hash", this.hash);
} }
async autoLogin(): Promise<boolean> { async autoLogin(): Promise<boolean> {
if (!this.server || !this.username) { if (!this.server || !this.username) {
return false; return false;
} }
return this.login(this.server, this.username, this.password, false) return this.loginWithHash(this.server, this.username, this.salt, this.hash, false)
.then(() => true) .then(() => true)
.catch(() => false); .catch(() => false);
} }
async login(server: string, username: string, password: string, remember: boolean) { async loginWithPassword(server: string, username: string, password: string, remember: boolean) {
return axios.get(`${server}/rest/ping.view?u=${username}&p=${password}&v=1.15.0&c=app&f=json`) const salt = randomString();
const hash = md5(password + salt);
return this.loginWithHash(server, username, salt, hash, remember);
}
private async loginWithHash(server: string, username: string, salt: string, hash: string, remember: boolean) {
return axios.get(`${server}/rest/ping.view?u=${username}&s=${salt}&t=${hash}&v=1.15.0&c=app&f=json`)
.then((response) => { .then((response) => {
const subsonicResponse = response.data["subsonic-response"]; const subsonicResponse = response.data["subsonic-response"];
if (!subsonicResponse || subsonicResponse.status !== "ok") { if (!subsonicResponse || subsonicResponse.status !== "ok") {
@ -39,7 +49,8 @@ export class AuthService {
this.authenticated = true; this.authenticated = true;
this.server = server; this.server = server;
this.username = username; this.username = username;
this.password = password; this.salt = salt;
this.hash = hash;
if (remember) { if (remember) {
this.saveSession(); this.saveSession();
} }

View File

@ -16,7 +16,8 @@ export class API {
config.params = config.params || {}; config.params = config.params || {};
config.baseURL = this.auth.server config.baseURL = this.auth.server
config.params.u = this.auth.username; config.params.u = this.auth.username;
config.params.p = this.auth.password; config.params.s = this.auth.salt;
config.params.t = this.auth.hash;
config.params.c = "app"; config.params.c = "app";
config.params.f = "json"; config.params.f = "json";
config.params.v = "1.15.0"; config.params.v = "1.15.0";
@ -255,12 +256,12 @@ export class API {
if (!item.coverArt) { if (!item.coverArt) {
return undefined; return undefined;
} }
const { server, username, password } = this.auth; const { server, username, salt, hash } = this.auth;
return `${server}/rest/getCoverArt?id=${item.coverArt}&v=1.15.0&u=${username}&p=${password}&c=test&size=300` return `${server}/rest/getCoverArt?id=${item.coverArt}&v=1.15.0&u=${username}&s=${salt}&t=${hash}&c=test&size=300`
} }
private getStreamUrl(id: any) { private getStreamUrl(id: any) {
const { server, username, password } = this.auth; const { server, username, salt, hash } = this.auth;
return `${server}/rest/stream?id=${id}&format=raw&v=1.15.0&u=${username}&p=${password}&c=test&size=300` return `${server}/rest/stream?id=${id}&format=raw&v=1.15.0&u=${username}&s=${salt}&t=${hash}&c=test&size=300`
} }
} }

13
src/shared/utils.ts Normal file
View File

@ -0,0 +1,13 @@
import MD5 from 'md5-es';
export function randomString(): string {
let arr = new Uint8Array(16);
window.crypto.getRandomValues(arr);
const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
arr = arr.map(x => validChars.charCodeAt(x % validChars.length));
return String.fromCharCode.apply(null, Array.from(arr));
}
export function md5(str: string): string {
return MD5.hash(str);
}

2
src/shims-vue.d.ts vendored
View File

@ -2,3 +2,5 @@ declare module '*.vue' {
import Vue from 'vue' import Vue from 'vue'
export default Vue export default Vue
} }
declare module "md5-es";

View File

@ -5081,6 +5081,11 @@ material-design-icons-iconfont@^5.0.1:
resolved "https://registry.yarnpkg.com/material-design-icons-iconfont/-/material-design-icons-iconfont-5.0.1.tgz#371875ed7fe9c8c520bc7123c3231feeab731c31" resolved "https://registry.yarnpkg.com/material-design-icons-iconfont/-/material-design-icons-iconfont-5.0.1.tgz#371875ed7fe9c8c520bc7123c3231feeab731c31"
integrity sha512-Xg6rIdGrfySTqiTZ6d+nQbcFepS6R4uKbJP0oAqyeZXJY/bX6mZDnOmmUJusqLXfhIwirs0c++a6JpqVa8RFvA== integrity sha512-Xg6rIdGrfySTqiTZ6d+nQbcFepS6R4uKbJP0oAqyeZXJY/bX6mZDnOmmUJusqLXfhIwirs0c++a6JpqVa8RFvA==
md5-es@1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/md5-es/-/md5-es-1.8.2.tgz#4c3d07b3f8e70669afd00ba3ac8b4b619741b506"
integrity sha512-LKq5jmKMhJYhsBFUh2w+J3C4bMiC5uQie/UYJ429UATmMnFr6iANO2uQq5HXAZSIupGp0WO2mH3sNfxR4XO40Q==
md5.js@^1.3.4: md5.js@^1.3.4:
version "1.3.5" version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"