184 lines
6.1 KiB
Vala
184 lines
6.1 KiB
Vala
|
/* setup.vala
|
||
|
*
|
||
|
* Copyright 2024 Erica Z
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU Affero General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU Affero General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Affero General Public License
|
||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
*/
|
||
|
|
||
|
[CCode (cname = "sqlite3_randomness")]
|
||
|
public extern void randomness (int N, void *P);
|
||
|
|
||
|
[GtkTemplate (ui = "/eu/callcc/Wavelet/setup.ui")]
|
||
|
public class Wavelet.Setup : Adw.NavigationPage {
|
||
|
[GtkChild] private unowned Adw.EntryRow server_url;
|
||
|
[GtkChild] private unowned Adw.EntryRow username;
|
||
|
[GtkChild] private unowned Adw.PasswordEntryRow password;
|
||
|
|
||
|
[GtkChild] private unowned Adw.ActionRow status;
|
||
|
[GtkChild] private unowned Adw.ButtonRow validate;
|
||
|
|
||
|
private string token;
|
||
|
private string salt;
|
||
|
|
||
|
public signal void connected (Wavelet.Subsonic api);
|
||
|
|
||
|
construct {
|
||
|
this.server_url.changed.connect (() => {
|
||
|
this.validate.set_sensitive (true);
|
||
|
});
|
||
|
this.username.changed.connect (() => {
|
||
|
this.validate.set_sensitive (true);
|
||
|
});
|
||
|
this.password.changed.connect (() => {
|
||
|
this.validate.set_sensitive (true);
|
||
|
});
|
||
|
|
||
|
this.validate.activated.connect (() => {
|
||
|
this.server_url.set_sensitive (false);
|
||
|
this.username.set_sensitive (false);
|
||
|
this.password.set_sensitive (false);
|
||
|
this.status.set_subtitle ("Connecting...");
|
||
|
this.validate.set_sensitive (false);
|
||
|
|
||
|
string new_token, new_salt;
|
||
|
if (this.password.get_text () != "") {
|
||
|
this.salt_password (this.password.get_text (), out new_token, out new_salt);
|
||
|
} else {
|
||
|
new_token = this.token;
|
||
|
new_salt = this.salt;
|
||
|
}
|
||
|
var api = new Wavelet.Subsonic.with_token (
|
||
|
this.server_url.get_text (),
|
||
|
this.username.get_text (),
|
||
|
new_token,
|
||
|
new_salt);
|
||
|
|
||
|
api.ping.begin ((obj, res) => {
|
||
|
try {
|
||
|
api.ping.end (res);
|
||
|
this.status.set_subtitle ("Connected");
|
||
|
this.token = new_token;
|
||
|
this.salt = new_salt;
|
||
|
this.save ();
|
||
|
this.validate.set_sensitive (false);
|
||
|
|
||
|
this.connected (api);
|
||
|
} catch (Error e) {
|
||
|
this.status.set_subtitle(@"Ping failed: $(e.message)");
|
||
|
this.validate.set_sensitive (true);
|
||
|
}
|
||
|
|
||
|
this.server_url.set_sensitive (true);
|
||
|
this.username.set_sensitive (true);
|
||
|
this.password.set_sensitive (true);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
public void load (Sqlite.Database db) {
|
||
|
Sqlite.Statement stmt;
|
||
|
int rc;
|
||
|
|
||
|
rc = db.prepare_v2 ("""SELECT value FROM Setup WHERE key IS ?1""", -1, out stmt);
|
||
|
assert (rc == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "server_url");
|
||
|
if (stmt.step () == Sqlite.ROW) {
|
||
|
this.server_url.set_text (stmt.column_text (0));
|
||
|
} else {
|
||
|
this.server_url.set_text ("");
|
||
|
}
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "username");
|
||
|
if (stmt.step () == Sqlite.ROW) {
|
||
|
this.username.set_text (stmt.column_text (0));
|
||
|
} else {
|
||
|
this.username.set_text ("");
|
||
|
}
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "token");
|
||
|
if (stmt.step () == Sqlite.ROW) {
|
||
|
this.token = stmt.column_text (0);
|
||
|
} else {
|
||
|
this.token = "";
|
||
|
}
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "salt");
|
||
|
if (stmt.step () == Sqlite.ROW) {
|
||
|
this.salt = stmt.column_text (0);
|
||
|
} else {
|
||
|
this.salt = "";
|
||
|
}
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
this.password.set_text ("");
|
||
|
this.validate.set_sensitive (false);
|
||
|
|
||
|
// first connection
|
||
|
this.validate.activate ();
|
||
|
}
|
||
|
|
||
|
private void salt_password (string password, out string token, out string salt) {
|
||
|
uchar salt_bytes[8];
|
||
|
randomness (8, salt_bytes);
|
||
|
uchar salt_chars[17];
|
||
|
for (int i = 0; i < 8; i += 1) {
|
||
|
salt_chars[2*i+0] = "0123456789abcdef"[(salt_bytes[i]>>4)&0xf];
|
||
|
salt_chars[2*i+1] = "0123456789abcdef"[(salt_bytes[i]>>0)&0xf];
|
||
|
}
|
||
|
salt_chars[16] = 0;
|
||
|
var checksum = new Checksum (ChecksumType.MD5);
|
||
|
checksum.update ((uchar[]) password, -1);
|
||
|
checksum.update (salt_chars, 16);
|
||
|
|
||
|
token = checksum.get_string ();
|
||
|
salt = (string) salt_chars;
|
||
|
}
|
||
|
|
||
|
public void save () {
|
||
|
Sqlite.Statement stmt;
|
||
|
int rc;
|
||
|
|
||
|
rc = config_db.prepare_v2 ("""INSERT OR REPLACE INTO Setup VALUES (?1, ?2)""", -1, out stmt);
|
||
|
assert (rc == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "server_url");
|
||
|
stmt.bind_text (2, this.server_url.get_text ());
|
||
|
assert (stmt.step () == Sqlite.DONE);
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "username");
|
||
|
stmt.bind_text (2, this.username.get_text ());
|
||
|
assert (stmt.step () == Sqlite.DONE);
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "token");
|
||
|
stmt.bind_text (2, this.token);
|
||
|
assert (stmt.step () == Sqlite.DONE);
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
stmt.bind_text (1, "salt");
|
||
|
stmt.bind_text (2, this.salt);
|
||
|
assert (stmt.step () == Sqlite.DONE);
|
||
|
assert (stmt.reset () == Sqlite.OK);
|
||
|
|
||
|
this.password.set_text ("");
|
||
|
}
|
||
|
}
|