Very WIP changes from DeSo structs to LBRY structs
This commit is contained in:
parent
6b6efe6ce0
commit
a3023dc40c
10 changed files with 113 additions and 117 deletions
|
@ -1,18 +1,14 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {CryptoService} from './crypto.service';
|
import {CryptoService} from './crypto.service';
|
||||||
import {GlobalVarsService} from './global-vars.service';
|
import {GlobalVarsService} from './global-vars.service';
|
||||||
import {AccessLevel, Network, PrivateUserInfo, PublicUserInfo} from '../types/identity';
|
import {AccessLevel, PrivateAccountInfo, PublicAccountInfo} from '../types/identity';
|
||||||
import HDKey from 'hdkey';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AccountService {
|
export class AccountService {
|
||||||
private static usersStorageKey = 'users';
|
|
||||||
private static levelsStorageKey = 'levels';
|
private static levelsStorageKey = 'levels';
|
||||||
|
|
||||||
private static publicKeyRegex = /^[a-zA-Z0-9]{54,55}$/;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private globalVars: GlobalVarsService,
|
private globalVars: GlobalVarsService,
|
||||||
|
@ -20,41 +16,70 @@ export class AccountService {
|
||||||
|
|
||||||
// Public Getters
|
// Public Getters
|
||||||
|
|
||||||
getPublicKeys(): any {
|
getAccountNames(): any {
|
||||||
return Object.keys(this.getPrivateUsers());
|
// TODO - maybe write this in a safer, more future-perfect way since it's converting
|
||||||
|
// private to public
|
||||||
|
return Object.keys(this.getWalletAccounts());
|
||||||
}
|
}
|
||||||
|
|
||||||
getEncryptedUsers(): {[key: string]: PublicUserInfo} {
|
// TODO - As of this writing, we want to share channel claim ids with the
|
||||||
|
// account on login, and spending addresses on request (probably with
|
||||||
|
// explicit permission)
|
||||||
|
//
|
||||||
|
// This is in a state in between what DeSo had and what
|
||||||
|
// we want ultimately for LBRY.
|
||||||
|
getPublicAccounts(): {[key: string]: PublicAccountInfo} {
|
||||||
const hostname = this.globalVars.hostname;
|
const hostname = this.globalVars.hostname;
|
||||||
const privateUsers = this.getPrivateUsers();
|
const privateAccounts = this.getWalletAccounts();
|
||||||
const publicUsers: {[key: string]: PublicUserInfo} = {};
|
const publicAccounts: {[key: string]: PublicAccountInfo} = {};
|
||||||
|
|
||||||
for (const publicKey of Object.keys(privateUsers)) {
|
for (const name of Object.keys(privateAccounts)) {
|
||||||
const privateUser = privateUsers[publicKey];
|
const privateAccount = privateAccounts[name];
|
||||||
const accessLevel = this.getAccessLevel(publicKey, hostname);
|
const accessLevel = this.getAccessLevel(name, hostname);
|
||||||
if (accessLevel === AccessLevel.None) {
|
if (accessLevel === AccessLevel.None) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptedSeedHex = this.cryptoService.encryptSeedHex(privateUser.seedHex, hostname);
|
// TODO
|
||||||
const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateUser.seedHex);
|
throw 'Implement the hmac properly'
|
||||||
|
|
||||||
publicUsers[publicKey] = {
|
// TODO - why do we even have hmac if everything's in local storage anyway?
|
||||||
hasExtraText: privateUser.extraText?.length > 0,
|
const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateAccount.seed);
|
||||||
encryptedSeedHex,
|
|
||||||
network: privateUser.network,
|
publicAccounts[name] = {
|
||||||
|
name,
|
||||||
|
network: privateAccount.ledger,
|
||||||
accessLevel,
|
accessLevel,
|
||||||
accessLevelHmac,
|
accessLevelHmac,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return publicUsers;
|
return publicAccounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccessLevel(publicKey: string, hostname: string): AccessLevel {
|
// TODO - Need to confirm that this works I think
|
||||||
|
public getWalletAccounts(): {[key: string]: PrivateAccountInfo} {
|
||||||
|
const wallet = this.cryptoService.getWallet(this.globalVars.hostname)
|
||||||
|
if (wallet === null) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const filteredAccounts: {[key: string]: PrivateAccountInfo} = {};
|
||||||
|
|
||||||
|
for (const account of wallet.accounts) {
|
||||||
|
// Only include accounts from the current network
|
||||||
|
if (account.ledger !== this.globalVars.network) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredAccounts[account.name] = account;
|
||||||
|
}
|
||||||
|
return filteredAccounts
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessLevel(accountName: string, hostname: string): AccessLevel {
|
||||||
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
||||||
const hostMapping = levels[hostname] || {};
|
const hostMapping = levels[hostname] || {};
|
||||||
const accessLevel = hostMapping[publicKey];
|
const accessLevel = hostMapping[accountName];
|
||||||
|
|
||||||
if (Object.values(AccessLevel).includes(accessLevel)) {
|
if (Object.values(AccessLevel).includes(accessLevel)) {
|
||||||
return accessLevel;
|
return accessLevel;
|
||||||
|
@ -65,78 +90,25 @@ export class AccountService {
|
||||||
|
|
||||||
// Public Modifiers
|
// Public Modifiers
|
||||||
|
|
||||||
addUser(keychain: HDKey, mnemonic: string, extraText: string, network: Network): string {
|
setAccessLevel(accountName: string, hostname: string, accessLevel: AccessLevel): void {
|
||||||
const seedHex = this.cryptoService.keychainToSeedHex(keychain);
|
|
||||||
|
|
||||||
return this.addPrivateUser({
|
|
||||||
seedHex,
|
|
||||||
mnemonic,
|
|
||||||
extraText,
|
|
||||||
network,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteUser(publicKey: string): void {
|
|
||||||
const privateUsers = this.getPrivateUsersRaw();
|
|
||||||
|
|
||||||
delete privateUsers[publicKey];
|
|
||||||
|
|
||||||
this.setPrivateUsersRaw(privateUsers);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAccessLevel(publicKey: string, hostname: string, accessLevel: AccessLevel): void {
|
|
||||||
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
||||||
|
|
||||||
levels[hostname] ||= {};
|
levels[hostname] ||= {};
|
||||||
levels[hostname][publicKey] = accessLevel;
|
levels[hostname][accountName] = accessLevel;
|
||||||
|
|
||||||
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
|
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private Getters and Modifiers
|
// log out of hostname entirely by setting accesslevel to None
|
||||||
|
resetAccessLevels(hostname: string): void {
|
||||||
|
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
||||||
|
|
||||||
// TEMP: public for import flow
|
levels[hostname] ||= {};
|
||||||
public addPrivateUser(userInfo: PrivateUserInfo): string {
|
for (const accountName in levels[hostname]) {
|
||||||
const privateUsers = this.getPrivateUsersRaw();
|
levels[hostname][accountName] = AccessLevel.None;
|
||||||
const privateKey = this.cryptoService.seedHexToPrivateKey(userInfo.seedHex);
|
|
||||||
const publicKey = this.cryptoService.privateKeyToDeSoPublicKey(privateKey, userInfo.network);
|
|
||||||
|
|
||||||
privateUsers[publicKey] = userInfo;
|
|
||||||
|
|
||||||
this.setPrivateUsersRaw(privateUsers);
|
|
||||||
|
|
||||||
return publicKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPrivateUsers(): {[key: string]: PrivateUserInfo} {
|
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
|
||||||
const privateUsers = this.getPrivateUsersRaw();
|
|
||||||
const filteredPrivateUsers: {[key: string]: PrivateUserInfo} = {};
|
|
||||||
|
|
||||||
for (const publicKey of Object.keys(privateUsers)) {
|
|
||||||
const privateUser = privateUsers[publicKey];
|
|
||||||
|
|
||||||
// Only include users from the current network
|
|
||||||
if (privateUser.network !== this.globalVars.network) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get rid of some users who have invalid public keys
|
|
||||||
if (!publicKey.match(AccountService.publicKeyRegex)) {
|
|
||||||
this.deleteUser(publicKey);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredPrivateUsers[publicKey] = privateUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredPrivateUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPrivateUsersRaw(): {[key: string]: PrivateUserInfo} {
|
|
||||||
return JSON.parse(localStorage.getItem(AccountService.usersStorageKey) || '{}');
|
|
||||||
}
|
|
||||||
|
|
||||||
private setPrivateUsersRaw(privateUsers: {[key: string]: PrivateUserInfo}): void {
|
|
||||||
localStorage.setItem(AccountService.usersStorageKey, JSON.stringify(privateUsers));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.get('testnet')) {
|
if (params.get('testnet')) {
|
||||||
this.globalVars.network = Network.testnet;
|
this.globalVars.network = Network.TestNet;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback should only be used in mobile applications, where payload is passed through URL parameters.
|
// Callback should only be used in mobile applications, where payload is passed through URL parameters.
|
||||||
|
|
|
@ -84,7 +84,7 @@ export class ApproveComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(signedTransactionHex?: string): void {
|
finishFlow(signedTransactionHex?: string): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
users: this.accountService.getEncryptedUsers(),
|
accounts: this.accountService.getPublicAccounts(),
|
||||||
signedTransactionHex,
|
signedTransactionHex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {environment} from '../environments/environment';
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class GlobalVarsService {
|
export class GlobalVarsService {
|
||||||
network = Network.mainnet;
|
network : Network = Network.MainNet;
|
||||||
hostname = '';
|
hostname = '';
|
||||||
accessLevelRequest = AccessLevel.ApproveAll;
|
accessLevelRequest = AccessLevel.ApproveAll;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {Observable, Subject} from 'rxjs';
|
import {Observable, Subject} from 'rxjs';
|
||||||
import {v4 as uuid} from 'uuid';
|
import {v4 as uuid} from 'uuid';
|
||||||
import {AccessLevel, PublicUserInfo} from '../types/identity';
|
import {AccessLevel, PublicAccountInfo} from '../types/identity';
|
||||||
import {CryptoService} from './crypto.service';
|
import {CryptoService} from './crypto.service';
|
||||||
import {GlobalVarsService} from './global-vars.service';
|
import {GlobalVarsService} from './global-vars.service';
|
||||||
import {CookieService} from 'ngx-cookie';
|
import {CookieService} from 'ngx-cookie';
|
||||||
|
@ -65,8 +65,8 @@ export class IdentityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
login(payload: {
|
login(payload: {
|
||||||
users: {[key: string]: PublicUserInfo},
|
accounts: {[key: string]: PublicAccountInfo}, // Channel ids
|
||||||
publicKeyAdded?: string,
|
accountNameAdded?: string, // Which channel id was just authorized. the app may allow the user to switch between them, but this determines which is on now.
|
||||||
signedUp?: boolean
|
signedUp?: boolean
|
||||||
signedTransactionHex?: string,
|
signedTransactionHex?: string,
|
||||||
addresses?: string[],
|
addresses?: string[],
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
|
||||||
import {CryptoService} from '../crypto.service';
|
import {CryptoService} from '../crypto.service';
|
||||||
import {IdentityService} from '../identity.service';
|
import {IdentityService} from '../identity.service';
|
||||||
import {AccountService} from '../account.service';
|
import {AccountService} from '../account.service';
|
||||||
import {AccessLevel} from '../../types/identity';
|
|
||||||
import {GlobalVarsService} from '../global-vars.service';
|
import {GlobalVarsService} from '../global-vars.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -20,7 +18,6 @@ export class LogoutComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private identityService: IdentityService,
|
private identityService: IdentityService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
|
@ -28,9 +25,6 @@ export class LogoutComponent implements OnInit {
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.activatedRoute.queryParams.subscribe(params => {
|
|
||||||
this.publicKey = params.publicKey || '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel(): void {
|
onCancel(): void {
|
||||||
|
@ -39,7 +33,7 @@ export class LogoutComponent implements OnInit {
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
// We set the accessLevel for the logged out user to None.
|
// We set the accessLevel for the logged out user to None.
|
||||||
this.accountService.setAccessLevel(this.publicKey, this.globalVars.hostname, AccessLevel.None);
|
this.accountService.resetAccessLevels(this.globalVars.hostname);
|
||||||
// We reset the seed encryption key so that all existing accounts, except
|
// We reset the seed encryption key so that all existing accounts, except
|
||||||
// the logged out user, will regenerate their encryptedSeedHex. Without this,
|
// the logged out user, will regenerate their encryptedSeedHex. Without this,
|
||||||
// someone could have reused the encryptedSeedHex of an already logged out user.
|
// someone could have reused the encryptedSeedHex of an already logged out user.
|
||||||
|
@ -49,7 +43,7 @@ export class LogoutComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(): void {
|
finishFlow(): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
users: this.accountService.getEncryptedUsers(),
|
accounts: this.accountService.getPublicAccounts(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export class TestLbryLogInComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(addresses: string[]): void {
|
finishFlow(addresses: string[]): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
users: {}, // TODO sigh
|
accounts: this.accountService.getPublicAccounts(),
|
||||||
addresses,
|
addresses,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import {AccountService} from '../account.service';
|
||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {SigningService} from '../signing.service';
|
import {SigningService} from '../signing.service';
|
||||||
import {IdentityService} from '../identity.service';
|
import {IdentityService} from '../identity.service';
|
||||||
import {CryptoService} from '../crypto.service';
|
import {CryptoService} from '../crypto.service';
|
||||||
|
@ -14,6 +15,7 @@ export class TestSignTransactionComponent implements OnInit {
|
||||||
signedTransactionHex?: string
|
signedTransactionHex?: string
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private accountService: AccountService,
|
||||||
private signingService: SigningService,
|
private signingService: SigningService,
|
||||||
private identityService: IdentityService,
|
private identityService: IdentityService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
|
@ -39,7 +41,7 @@ export class TestSignTransactionComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(signedTransactionHex?: string): void {
|
finishFlow(signedTransactionHex?: string): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
users: {}, // TODO sigh
|
accounts: this.accountService.getPublicAccounts(),
|
||||||
signedTransactionHex,
|
signedTransactionHex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import {AccountService} from '../account.service';
|
||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {SigningService} from '../signing.service';
|
import {SigningService} from '../signing.service';
|
||||||
import {IdentityService} from '../identity.service';
|
import {IdentityService} from '../identity.service';
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ export class TestSignComponent implements OnInit {
|
||||||
privateKeyString: string = "thhUUVXQtyxonMaezCKwihLw9tZUrGJgWBMxDNfxoWub8dLGA"
|
privateKeyString: string = "thhUUVXQtyxonMaezCKwihLw9tZUrGJgWBMxDNfxoWub8dLGA"
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private accountService: AccountService,
|
||||||
private signingService: SigningService,
|
private signingService: SigningService,
|
||||||
private identityService: IdentityService,
|
private identityService: IdentityService,
|
||||||
) { }
|
) { }
|
||||||
|
@ -34,7 +36,7 @@ export class TestSignComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(signatureHex?: string): void {
|
finishFlow(signatureHex?: string): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
users: {}, // TODO sigh
|
accounts: this.accountService.getPublicAccounts(),
|
||||||
signatureHex,
|
signatureHex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,47 @@
|
||||||
export interface PrivateUserInfo {
|
// TODO: Use our wallet json-schema. Otherwise, bitcoinjs-lib probably already has a networks enum.
|
||||||
seedHex: string;
|
// TODO: what about encrypted wallets?
|
||||||
mnemonic: string;
|
|
||||||
extraText: string;
|
|
||||||
network: Network;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PublicUserInfo {
|
export enum AddressType {
|
||||||
hasExtraText: boolean;
|
DeterministicChain = "deterministic-chain",
|
||||||
encryptedSeedHex: string;
|
SingleAddress = "single-address",
|
||||||
network: Network;
|
|
||||||
accessLevel: AccessLevel;
|
|
||||||
accessLevelHmac: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Network {
|
export enum Network {
|
||||||
mainnet = 'mainnet',
|
MainNet = "lbc_mainnet",
|
||||||
testnet = 'testnet',
|
TestNet = "lbc_testnet",
|
||||||
|
RegTest = "lbc_regtest",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only safe for web wallet, not sent to the app
|
||||||
|
export interface PrivateAccountInfo {
|
||||||
|
address_generator: {
|
||||||
|
change?: {
|
||||||
|
gap: number,
|
||||||
|
maximum_uses_per_address: number,
|
||||||
|
},
|
||||||
|
name: AddressType,
|
||||||
|
receiving?: {
|
||||||
|
gap: number,
|
||||||
|
maximum_uses_per_address: number,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
certificates: {[key: string]: string},
|
||||||
|
encrypted: boolean,
|
||||||
|
ledger: Network,
|
||||||
|
modified_on: number,
|
||||||
|
name: string,
|
||||||
|
private_key: string,
|
||||||
|
public_key: string,
|
||||||
|
seed: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// can be sent to the app
|
||||||
|
export interface PublicAccountInfo {
|
||||||
|
// TODO - add more useful stuff
|
||||||
|
name: string;
|
||||||
|
network: Network;
|
||||||
|
accessLevel: AccessLevel;
|
||||||
|
accessLevelHmac: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AccessLevel {
|
export enum AccessLevel {
|
||||||
|
|
Loading…
Reference in a new issue