forked from cory/tildefriends
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			5474c5a101
			...
			user_setti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 58dbf42a3a | |||
| a1f221879b | |||
| 2a928dcafc | 
@@ -1,5 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
	"type": "tildefriends-app",
 | 
			
		||||
	"emoji": "🪪",
 | 
			
		||||
	"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
import * as tfrpc from '/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
tfrpc.register(async function get_private_key(id) {
 | 
			
		||||
	return bip39Words(await ssb.getPrivateKey(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function create_id(id) {
 | 
			
		||||
	return await ssb.createIdentity();
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function add_id(id) {
 | 
			
		||||
	return await ssb.addIdentity(bip39Bytes(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function delete_id(id) {
 | 
			
		||||
	return await ssb.deleteIdentity(id);
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function reload() {
 | 
			
		||||
	await main();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
	let ids = await ssb.getIdentities();
 | 
			
		||||
	await app.setDocument(
 | 
			
		||||
		`<body style="color: #fff">
 | 
			
		||||
		<script>const handler = {};</script>
 | 
			
		||||
		<script type="module">
 | 
			
		||||
			import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
			handler.export_id = async function export_id(event) {
 | 
			
		||||
				let id = event.srcElement.dataset.id;
 | 
			
		||||
				let element = document.createElement('textarea');
 | 
			
		||||
				element.value = await tfrpc.rpc.get_private_key(id);
 | 
			
		||||
				element.style = 'width: 100%; read-only: true';
 | 
			
		||||
				element.readOnly = true;
 | 
			
		||||
				event.srcElement.parentElement.appendChild(element);
 | 
			
		||||
				event.srcElement.onclick = event => handler.hide_id(event, element);
 | 
			
		||||
			}
 | 
			
		||||
			handler.add_id = async function add_id(event) {
 | 
			
		||||
				let id = document.getElementById('add_id').value;
 | 
			
		||||
				try {
 | 
			
		||||
					let new_id = await tfrpc.rpc.add_id(id);
 | 
			
		||||
					alert('Successfully imported: ' + new_id);
 | 
			
		||||
					await tfrpc.rpc.reload();
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					alert('Error importing identity: ' + e);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			handler.create_id = async function create_id(event) {
 | 
			
		||||
				try {
 | 
			
		||||
					let id = await tfrpc.rpc.create_id();
 | 
			
		||||
					alert('Successfully created: ' + id);
 | 
			
		||||
					await tfrpc.rpc.reload();
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					alert('Error creating identity: ' + e);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			handler.hide_id = function hide_id(event, element) {
 | 
			
		||||
				element.parentNode.removeChild(element);
 | 
			
		||||
				event.srcElement.onclick = handler.export_id;
 | 
			
		||||
			}
 | 
			
		||||
			handler.delete_id = async function delete_id(event) {
 | 
			
		||||
				let id = event.srcElement.dataset.id;
 | 
			
		||||
				try {
 | 
			
		||||
					if (prompt('Are you sure you want to delete "' + id + '"?  It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.') === 'DELETE') {
 | 
			
		||||
						if (await tfrpc.rpc.delete_id(id)) {
 | 
			
		||||
							alert('Successfully deleted ID: ' + id);
 | 
			
		||||
						}
 | 
			
		||||
						await tfrpc.rpc.reload();
 | 
			
		||||
					}
 | 
			
		||||
				} catch (e) {
 | 
			
		||||
					alert('Error deleting ID: ' + e);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
		<h1>SSB Identity Management</h1>
 | 
			
		||||
		<h2>Create a new identity</h2>
 | 
			
		||||
		<button id="create_id" onclick="handler.create_id()">Create Identity</button>
 | 
			
		||||
		<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
 | 
			
		||||
		<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
 | 
			
		||||
		<h2>Identities</h2>
 | 
			
		||||
		<ul>` +
 | 
			
		||||
			ids
 | 
			
		||||
				.map(
 | 
			
		||||
					(id) => `<li>
 | 
			
		||||
			<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
 | 
			
		||||
			<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
 | 
			
		||||
			${id}
 | 
			
		||||
		</li>`
 | 
			
		||||
				)
 | 
			
		||||
				.join('\n') +
 | 
			
		||||
			`	</ul>
 | 
			
		||||
	</body>`
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
main();
 | 
			
		||||
@@ -1,4 +1 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "tildefriends-app",
 | 
			
		||||
  "emoji": "⚙️"
 | 
			
		||||
}
 | 
			
		||||
{"type": "tildefriends-app", "emoji": "⚙️"}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,23 @@ tfrpc.register(async function createID(id) {
 | 
			
		||||
tfrpc.register(async function getPrivateKey(id) {
 | 
			
		||||
	return bip39Words(await ssb.getPrivateKey(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function addID(id) {
 | 
			
		||||
	return await ssb.addIdentity(bip39Bytes(id));
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function deleteID(id) {
 | 
			
		||||
	return await ssb.deleteIdentity(id);
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function getThemes() {
 | 
			
		||||
	// TODO
 | 
			
		||||
	return ['solarized', 'gruvbox', 'light'];
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function getTheme() {
 | 
			
		||||
	// TODO
 | 
			
		||||
	return 'gruvbox';
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function setTheme() {
 | 
			
		||||
	// TODO
 | 
			
		||||
	console.warn("setTheme called - not implemented")
 | 
			
		||||
	console.warn('setTheme called - not implemented');
 | 
			
		||||
	return null;
 | 
			
		||||
});
 | 
			
		||||
tfrpc.register(async function reload() {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
 | 
			
		||||
	<h3>Manage your identities</h3>
 | 
			
		||||
	<tf-identity-manager></tf-identity-manager>
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	<h3>Change my password</h3>
 | 
			
		||||
	<tf-password-form></tf-password-form>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
/* */
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
/* */
 | 
			
		||||
@@ -22,7 +22,7 @@ class TfDeleteAccountButtonElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
 | 
			
		||||
			<span>This action is irreversible !</span>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ class TfIdentityManagerElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
	async createIdentity() {
 | 
			
		||||
		try {
 | 
			
		||||
			let id = await tfrpc.rpc.createID();
 | 
			
		||||
			const id = await tfrpc.rpc.createID();
 | 
			
		||||
			alert('Successfully created: ' + id);
 | 
			
		||||
			await tfrpc.rpc.reload();
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
@@ -28,13 +28,50 @@ class TfIdentityManagerElement extends LitElement {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async importIdentity() {
 | 
			
		||||
		const words = this.renderRoot?.querySelector('#import-id-textarea').value;
 | 
			
		||||
		if (!words) return;
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			const newID = await tfrpc.rpc.addID(words);
 | 
			
		||||
 | 
			
		||||
			if (newID) alert('Successfully imported a new identity.');
 | 
			
		||||
			else alert('This identity already exists or is invalid.');
 | 
			
		||||
			await tfrpc.rpc.reload();
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			alert('Error importing identity: ' + err);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async exportIdentity(id) {
 | 
			
		||||
		alert('Your private key is:\n' + (await tfrpc.rpc.getPrivateKey(id)));
 | 
			
		||||
		alert(
 | 
			
		||||
			'Your private key is:\n' +
 | 
			
		||||
				(await tfrpc.rpc.getPrivateKey(id)) +
 | 
			
		||||
				'\nDo not share it with anyone!'
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async deleteIdentity(id) {
 | 
			
		||||
		try {
 | 
			
		||||
			if (
 | 
			
		||||
				prompt(
 | 
			
		||||
					'Are you sure you want to delete "' +
 | 
			
		||||
						id +
 | 
			
		||||
						'"?  It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.'
 | 
			
		||||
				) === 'DELETE'
 | 
			
		||||
			) {
 | 
			
		||||
				if (await tfrpc.rpc.deleteID(id)) {
 | 
			
		||||
					alert('Successfully deleted ID: ' + id);
 | 
			
		||||
				}
 | 
			
		||||
				await tfrpc.rpc.reload();
 | 
			
		||||
			}
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			alert('Error deleting ID: ' + e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
 | 
			
		||||
		return html` <link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
			<style>
 | 
			
		||||
				.id-span {
 | 
			
		||||
					font-family: monospace;
 | 
			
		||||
@@ -43,29 +80,39 @@ class TfIdentityManagerElement extends LitElement {
 | 
			
		||||
			</style>
 | 
			
		||||
 | 
			
		||||
			<h4>Create a new identity</h4>
 | 
			
		||||
			<button id="create-id" class="green" @click=${this.createIdentity}>Create Identity</button>
 | 
			
		||||
			<button id="create-id" class="green" @click=${this.createIdentity}>
 | 
			
		||||
				Create Identity
 | 
			
		||||
			</button>
 | 
			
		||||
 | 
			
		||||
			<h4>Import an SSB Identity from 12 BIP39 English Words</h4>
 | 
			
		||||
			<textarea id="add-id" style="width: 100%" rows="4"></textarea>
 | 
			
		||||
			<button class="green">[Not implemented] Import Identity</button>
 | 
			
		||||
			<textarea id="import-id-textarea" style="width: 100%" rows="4"></textarea>
 | 
			
		||||
			<button class="green" @click=${this.importIdentity}>
 | 
			
		||||
				Import Identity
 | 
			
		||||
			</button>
 | 
			
		||||
 | 
			
		||||
			<h4>Warning !</h4>
 | 
			
		||||
			<strong>Anybody that has access to your private key can gain total access over your account.</strong>
 | 
			
		||||
			<br><br>
 | 
			
		||||
			<strong
 | 
			
		||||
				>Anybody that knows your private key can gain total access over your
 | 
			
		||||
				account.</strong
 | 
			
		||||
			>
 | 
			
		||||
			<br /><br />
 | 
			
		||||
			Tilde Friends' contributors will never ask you for your private key !
 | 
			
		||||
 | 
			
		||||
			<ul>
 | 
			
		||||
			${this.ids.map(
 | 
			
		||||
				(id) =>
 | 
			
		||||
					html`
 | 
			
		||||
				<li>
 | 
			
		||||
					<button class="blue" @click=${() => this.exportIdentity(id)}>Export Identity</button>
 | 
			
		||||
					<button class="red">[Not implemented] Delete Identity</button>
 | 
			
		||||
					<span class="id-span">${id}</span>
 | 
			
		||||
				</li>`
 | 
			
		||||
			)}
 | 
			
		||||
				${this.ids.map(
 | 
			
		||||
					(id) =>
 | 
			
		||||
						html` <li>
 | 
			
		||||
							<button class="blue" @click=${() => this.exportIdentity(id)}>
 | 
			
		||||
								Export Identity
 | 
			
		||||
							</button>
 | 
			
		||||
							<button class="red" @click=${() => this.deleteIdentity(id)}>
 | 
			
		||||
								Delete Identity
 | 
			
		||||
							</button>
 | 
			
		||||
							<span class="id-span">${id}</span>
 | 
			
		||||
						</li>`
 | 
			
		||||
				)}
 | 
			
		||||
			</ul>`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-identity-manager', TfIdentityManagerElement);
 | 
			
		||||
customElements.define('tf-identity-manager', TfIdentityManagerElement);
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ class TfPasswordFormElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
 | 
			
		||||
			<style>
 | 
			
		||||
				.grid {
 | 
			
		||||
@@ -48,21 +48,35 @@ class TfPasswordFormElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
			<div class="grid">
 | 
			
		||||
				<label for="current">Current password:</label>
 | 
			
		||||
				<input type="password" id="current" name="current" autocomplete="current-password" />
 | 
			
		||||
				<input
 | 
			
		||||
					type="password"
 | 
			
		||||
					id="current"
 | 
			
		||||
					name="current"
 | 
			
		||||
					autocomplete="current-password"
 | 
			
		||||
				/>
 | 
			
		||||
 | 
			
		||||
				<label for="new">Enter new password:</label>
 | 
			
		||||
				<input type="password" id="new" name="new" autocomplete="new-password" />
 | 
			
		||||
				<input
 | 
			
		||||
					type="password"
 | 
			
		||||
					id="new"
 | 
			
		||||
					name="new"
 | 
			
		||||
					autocomplete="new-password"
 | 
			
		||||
				/>
 | 
			
		||||
 | 
			
		||||
				<label for="repeat">Repeat new password:</label>
 | 
			
		||||
				<input type="password" id="repeat" name="repeat" autocomplete="new-password" />
 | 
			
		||||
				<input
 | 
			
		||||
					type="password"
 | 
			
		||||
					id="repeat"
 | 
			
		||||
					name="repeat"
 | 
			
		||||
					autocomplete="new-password"
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<button @click=${this.submitPassword} class="red">
 | 
			
		||||
				[Not implemented] Change my password
 | 
			
		||||
			</button>
 | 
			
		||||
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('tf-password-form', TfPasswordFormElement);
 | 
			
		||||
customElements.define('tf-password-form', TfPasswordFormElement);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {LitElement, html} from './lit-all.min.js';
 | 
			
		||||
import {LitElement, html, nothing} from './lit-all.min.js';
 | 
			
		||||
import * as tfrpc from '/static/tfrpc.js';
 | 
			
		||||
 | 
			
		||||
class TfThemePickerElement extends LitElement {
 | 
			
		||||
@@ -16,6 +16,10 @@ class TfThemePickerElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
	async load() {
 | 
			
		||||
		this.themes = await tfrpc.rpc.getThemes();
 | 
			
		||||
		this.selected = await tfrpc.rpc.getTheme();
 | 
			
		||||
 | 
			
		||||
		let select = this.renderRoot?.querySelector('#theme-select');
 | 
			
		||||
		select.value = this.selected;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	changed(event) {
 | 
			
		||||
@@ -26,12 +30,19 @@ class TfThemePickerElement extends LitElement {
 | 
			
		||||
 | 
			
		||||
	render() {
 | 
			
		||||
		return html`
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
 | 
			
		||||
			<link rel="stylesheet" href="/static/tildefriends-v1.css" />
 | 
			
		||||
 | 
			
		||||
			<label for="theme">[Not implemented] Choose your theme:</label>
 | 
			
		||||
 | 
			
		||||
			<select name="theme" ?hidden=${!this.themes?.length} @change=${this.changed}>
 | 
			
		||||
				${(this.themes ?? []).map((id) => html`<option value=${id}>${id}</option>`)}
 | 
			
		||||
			<select
 | 
			
		||||
				name="theme"
 | 
			
		||||
				id="theme-select"
 | 
			
		||||
				?hidden=${!this.themes?.length}
 | 
			
		||||
				@change=${this.changed}
 | 
			
		||||
			>
 | 
			
		||||
				${(this.themes ?? []).map(
 | 
			
		||||
					(name) => html`<option value=${name}>${name}</option>`
 | 
			
		||||
				)}
 | 
			
		||||
			</select>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 * and use this tag to import it:
 | 
			
		||||
 * <link rel="stylesheet" href="/static/tildefriends-v1.css"/>
 | 
			
		||||
 *
 | 
			
		||||
 * v1.0.0 / 2024 M03 21
 | 
			
		||||
 * v1.0 / 2024 M03 21
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
@@ -18,8 +18,8 @@ body {
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
.button,
 | 
			
		||||
input[type=button],
 | 
			
		||||
input[type=submit],
 | 
			
		||||
input[type='button'],
 | 
			
		||||
input[type='submit'],
 | 
			
		||||
select {
 | 
			
		||||
	border: none;
 | 
			
		||||
	border-radius: 8px;
 | 
			
		||||
@@ -75,7 +75,8 @@ table {
 | 
			
		||||
	width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td, th {
 | 
			
		||||
td,
 | 
			
		||||
th {
 | 
			
		||||
	border: 1px solid #ffffff40;
 | 
			
		||||
	text-align: left;
 | 
			
		||||
	padding: 8px;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user