Switch to sending Channel info to apps
Instead of account info. Stubbed out the hub work. Haven't made the example app accommodate it. Plenty left to do, but this lays down the order of things.
This commit is contained in:
parent
d0583e540a
commit
1c7854b9ec
16 changed files with 350 additions and 208 deletions
|
@ -1,70 +1,96 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {CryptoService} from './crypto.service';
|
|
||||||
import {GlobalVarsService} from './global-vars.service';
|
import {GlobalVarsService} from './global-vars.service';
|
||||||
import {AccessLevel, PrivateAccountInfo, PublicChannelInfo} from '../types/identity';
|
import {HubService} from './hub.service';
|
||||||
|
import {AccessLevel, ActionType, PrivateAccountInfo, PublicChannelInfo} from '../types/identity';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AccountService {
|
export class AccountService {
|
||||||
private static levelsStorageKey = 'levels';
|
private static walletStorageKey = 'wallet';
|
||||||
|
private static channelsStorageKey = 'channels';
|
||||||
|
private static accessStorageKey = 'access';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private globalVars: GlobalVarsService,
|
private globalVars: GlobalVarsService,
|
||||||
|
private hubService: HubService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
// Public Getters
|
/*
|
||||||
|
|
||||||
// TODO - As of this writing, we want to share channel claim ids with the
|
What we're keeping in local storage. TODO - make these into data structs.
|
||||||
// account on login, and spending addresses on request (probably with
|
|
||||||
// explicit permission)
|
|
||||||
getChannels(): {[key: string]: PublicChannelInfo} {
|
|
||||||
// TODO - will want for accessLevel stuff
|
|
||||||
// const hostname = this.globalVars.hostname;
|
|
||||||
|
|
||||||
const privateAccounts = this.getWalletAccounts();
|
// The wallet, taken from wallet Sync. Perhaps even with local changes to be
|
||||||
const channels: {[key: string]: PublicChannelInfo} = {};
|
// pushed back to sync.
|
||||||
|
localStorage["wallet"] = {
|
||||||
|
// We may later want to add signature, sync metadata, etc. Or maybe we want
|
||||||
|
// the strict string representation that came out of sync.
|
||||||
|
walletStoreVersion: 0,
|
||||||
|
|
||||||
for (const name of Object.keys(privateAccounts)) {
|
wallet: "...",
|
||||||
const privateAccount = privateAccounts[name];
|
|
||||||
for (const channelPubKeyAddress of Object.keys(privateAccount.certificates)) {
|
|
||||||
// TODO - For LBRY's purposes, not only will we want per-channel access
|
|
||||||
// levels, we'll want per channel per hostname per action access levels.
|
|
||||||
|
|
||||||
// TODO - finish when we have accessLevel stuff
|
|
||||||
/*
|
|
||||||
const accessLevel = this.getAccessLevel(name, hostname);
|
|
||||||
if (accessLevel === AccessLevel.None) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - Implement the hmac properly
|
|
||||||
// TODO - why do we even have hmac if everything's in local storage anyway?
|
|
||||||
const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateAccount.seed);
|
|
||||||
*/
|
|
||||||
|
|
||||||
channels[channelPubKeyAddress] = {
|
|
||||||
pubKeyAddress: channelPubKeyAddress,
|
|
||||||
network: privateAccount.ledger,
|
|
||||||
|
|
||||||
// TODO - fill in when we have accessLevel stuff
|
|
||||||
accessLevel: 0,
|
|
||||||
accessLevelHmac: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Need to confirm that this works I think
|
// Access information. For each hostname, what channel is logged in, and what
|
||||||
public getWalletAccounts(): {[key: string]: PrivateAccountInfo} {
|
// level of permission for various actions did the user give?
|
||||||
const wallet = this.cryptoService.getWallet(this.globalVars.hostname)
|
localStorage["access"] = {
|
||||||
if (wallet === null) {
|
// There can be multiple hostnames
|
||||||
return {}
|
"<hostnames>": { // TODO - type Hostname
|
||||||
|
"currentChannel": "<channel-claim-id>", // TODO - type ChannelClaimID
|
||||||
|
"levels": {
|
||||||
|
// There can be multiple channels
|
||||||
|
"<channel-claim-ids>": {
|
||||||
|
// There can be multiple action types
|
||||||
|
"<action-types>": "<level>",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const filteredAccounts: {[key: string]: PrivateAccountInfo} = {};
|
}
|
||||||
|
|
||||||
|
localStorage["channels"] = {
|
||||||
|
// There can be multiple channels
|
||||||
|
"<channel-claim-ids>": {
|
||||||
|
"claimId": "<channel-claim-id>",
|
||||||
|
"handle": "<channel-handle>",
|
||||||
|
"pubKeyAddress": "<channel-pub-key-address>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
private hasWallet(): boolean {
|
||||||
|
return !!localStorage.getItem(AccountService.walletStorageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO define a wallet type, and/or use a type defined by json-schema
|
||||||
|
public getWallet(): {accounts: [PrivateAccountInfo]} | null {
|
||||||
|
const walletStoreStr = localStorage.getItem(AccountService.walletStorageKey);
|
||||||
|
const walletStore = JSON.parse(walletStoreStr || 'null')
|
||||||
|
if (walletStore !== null) {
|
||||||
|
if (walletStore.walletStoreVersion === 0) {
|
||||||
|
return walletStore.wallet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private putWallet(wallet: object | null) {
|
||||||
|
const walletStore = {
|
||||||
|
// We may later want to add signature, sync metadata, etc. Or maybe we
|
||||||
|
// want the strict string representation that came out of sync. This is
|
||||||
|
// not the version of the wallet's internal structure; that has its own
|
||||||
|
// version key
|
||||||
|
walletStoreVersion: 0,
|
||||||
|
|
||||||
|
wallet
|
||||||
|
}
|
||||||
|
localStorage.setItem(AccountService.walletStorageKey, JSON.stringify(walletStore));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAccounts(): PrivateAccountInfo[] {
|
||||||
|
const wallet = this.getWallet()
|
||||||
|
if (wallet === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const filteredAccounts: PrivateAccountInfo[] = [];
|
||||||
|
|
||||||
for (const account of wallet.accounts) {
|
for (const account of wallet.accounts) {
|
||||||
// Only include accounts from the current network
|
// Only include accounts from the current network
|
||||||
|
@ -72,15 +98,74 @@ export class AccountService {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredAccounts[account.name] = account;
|
filteredAccounts.push(account);
|
||||||
}
|
}
|
||||||
return filteredAccounts
|
return filteredAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccessLevel(accountName: string, hostname: string): AccessLevel {
|
private clearChannels() {
|
||||||
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
localStorage.setItem(AccountService.channelsStorageKey, JSON.stringify(null));
|
||||||
const hostMapping = levels[hostname] || {};
|
}
|
||||||
const accessLevel = hostMapping[accountName];
|
|
||||||
|
public updateChannels() {
|
||||||
|
let xPubs: string[] = this.getAccounts().map(account => account.public_key)
|
||||||
|
let channels: {[key: string]: PublicChannelInfo} = {};
|
||||||
|
for (const hubChannel of this.hubService.getChannels(xPubs)) {
|
||||||
|
channels[hubChannel.claimId] = {
|
||||||
|
claimId: hubChannel.claimId,
|
||||||
|
handle: hubChannel.handle,
|
||||||
|
pubKeyAddress: hubChannel.pubKeyAddress,
|
||||||
|
// TODO -- more fields?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localStorage.setItem(AccountService.channelsStorageKey, JSON.stringify(channels));
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasChannels() {
|
||||||
|
return !!localStorage.getItem(AccountService.channelsStorageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChannels() {
|
||||||
|
return JSON.parse(localStorage.getItem(AccountService.channelsStorageKey) || '{}');
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearAccess() {
|
||||||
|
localStorage.setItem(AccountService.channelsStorageKey, JSON.stringify(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private initAccess() {
|
||||||
|
// no currentChannel or access level for any action on any hostname
|
||||||
|
localStorage.setItem(AccountService.accessStorageKey, '{}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasAccess() {
|
||||||
|
return !!localStorage.getItem(AccountService.accessStorageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getActiveChannel(hostname: string): PublicChannelInfo | null {
|
||||||
|
// TODO - and actually, this maybe only needs to happen on startup. could save in a local variable.
|
||||||
|
const channels = this.getChannels()
|
||||||
|
const access = JSON.parse(localStorage.getItem(AccountService.accessStorageKey) || '{}');
|
||||||
|
|
||||||
|
if (access[hostname]) {
|
||||||
|
const activeChannelClaimId = access[hostname].currentChannel
|
||||||
|
return channels[activeChannelClaimId]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
public getActiveChannelAccessLevel(hostname: string, action: ActionType): AccessLevel {
|
||||||
|
const access = JSON.parse(localStorage.getItem(AccountService.accessStorageKey) || '{}');
|
||||||
|
|
||||||
|
if (!access[hostname]) {
|
||||||
|
return AccessLevel.None
|
||||||
|
}
|
||||||
|
const activeChannelClaimId = access[hostname].currentChannel
|
||||||
|
if (!access[hostname].levels[activeChannelClaimId]) {
|
||||||
|
return AccessLevel.None
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessLevel = access[hostname].levels[activeChannelClaimId][action]
|
||||||
|
|
||||||
if (Object.values(AccessLevel).includes(accessLevel)) {
|
if (Object.values(AccessLevel).includes(accessLevel)) {
|
||||||
return accessLevel;
|
return accessLevel;
|
||||||
|
@ -89,27 +174,63 @@ export class AccountService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public Modifiers
|
public setAccessLevel(hostname: string, channelClaimId: string, action: ActionType, level: AccessLevel) {
|
||||||
|
const access = JSON.parse(localStorage.getItem(AccountService.accessStorageKey) || '{}');
|
||||||
setAccessLevel(accountName: string, hostname: string, accessLevel: AccessLevel): void {
|
if (!(hostname in access)) {
|
||||||
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
access[hostname] = {levels: {}}
|
||||||
|
}
|
||||||
levels[hostname] ||= {};
|
if (!(channelClaimId in access[hostname].levels)) {
|
||||||
levels[hostname][accountName] = accessLevel;
|
access[hostname].levels[channelClaimId] = {}
|
||||||
|
}
|
||||||
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
|
access[hostname].levels[channelClaimId][action] = level
|
||||||
|
localStorage.setItem(AccountService.accessStorageKey, JSON.stringify(access));
|
||||||
}
|
}
|
||||||
|
|
||||||
// log out of hostname entirely by setting accesslevel to None
|
public setAccessCurrentChannel(hostname: string, channelClaimId: string) {
|
||||||
resetAccessLevels(hostname: string): void {
|
const access = JSON.parse(localStorage.getItem(AccountService.accessStorageKey) || '{}');
|
||||||
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
|
if (!(hostname in access)) {
|
||||||
|
access[hostname] = {levels: {}}
|
||||||
levels[hostname] ||= {};
|
|
||||||
for (const accountName in levels[hostname]) {
|
|
||||||
levels[hostname][accountName] = AccessLevel.None;
|
|
||||||
}
|
}
|
||||||
|
access[hostname].currentChannel = channelClaimId
|
||||||
|
localStorage.setItem(AccountService.accessStorageKey, JSON.stringify(access));
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
|
public walletLogout() {
|
||||||
|
this.putWallet(null)
|
||||||
|
this.clearAccess()
|
||||||
|
this.clearChannels()
|
||||||
|
}
|
||||||
|
|
||||||
|
public walletLogin(wallet: object | null) {
|
||||||
|
// no ambiguity from half-completed actions
|
||||||
|
// make sure we're fully logged out
|
||||||
|
this.walletLogout()
|
||||||
|
|
||||||
|
this.putWallet(wallet)
|
||||||
|
this.initAccess()
|
||||||
|
this.updateChannels()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - delete this if I don't end up using it
|
||||||
|
public walletIsLoggedIn(): boolean {
|
||||||
|
return (
|
||||||
|
// All three should be set. It means the last login was complete, and no
|
||||||
|
// logout was started since.
|
||||||
|
this.hasAccess() && this.hasChannels() && this.hasWallet()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do we want to save per-hostname-per-channel-per-action access levels
|
||||||
|
// between logins? Counterargument: users probably won't log in and out too
|
||||||
|
// often.
|
||||||
|
private appClearAccess(hostname: string): void {
|
||||||
|
const access = JSON.parse(localStorage.getItem(AccountService.accessStorageKey) || '{}');
|
||||||
|
delete access[hostname]
|
||||||
|
localStorage.setItem(AccountService.accessStorageKey, JSON.stringify(access));
|
||||||
|
}
|
||||||
|
|
||||||
|
public appLogout(hostname: string): void {
|
||||||
|
this.appClearAccess(hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { EmbedComponent } from './embed/embed.component';
|
||||||
import { HomeComponent } from './home/home.component';
|
import { HomeComponent } from './home/home.component';
|
||||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
import {IdentityService} from './identity.service';
|
import {IdentityService} from './identity.service';
|
||||||
import {CookieModule} from 'ngx-cookie';
|
|
||||||
import { LogoutComponent } from './logout/logout.component';
|
import { LogoutComponent } from './logout/logout.component';
|
||||||
import { BannerComponent } from './banner/banner.component';
|
import { BannerComponent } from './banner/banner.component';
|
||||||
import { SignUpComponent } from './sign-up/sign-up.component';
|
import { SignUpComponent } from './sign-up/sign-up.component';
|
||||||
|
@ -50,7 +49,6 @@ import { LogInWalletComponent } from './log-in-wallet/log-in-wallet.component'
|
||||||
NgxIntlTelInputModule,
|
NgxIntlTelInputModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
CookieModule.forRoot()
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
IdentityService,
|
IdentityService,
|
||||||
|
|
|
@ -67,6 +67,9 @@ export class ApproveComponent implements OnInit {
|
||||||
this.publicKey = this.base58KeyCheck(this.transaction.publicKey);
|
this.publicKey = this.base58KeyCheck(this.transaction.publicKey);
|
||||||
|
|
||||||
this.generateTransactionDescription();
|
this.generateTransactionDescription();
|
||||||
|
/*
|
||||||
|
TODO this.accountService.getActiveChannelAccessLevel
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +85,10 @@ export class ApproveComponent implements OnInit {
|
||||||
const signedTransactionHex = this.signingService.signTransaction(seedHex, this.transactionHex);
|
const signedTransactionHex = this.signingService.signTransaction(seedHex, this.transactionHex);
|
||||||
this.finishFlow(signedTransactionHex);
|
this.finishFlow(signedTransactionHex);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO this.accountService.setAccessLevel if people want to keep allowing the action
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
finishFlow(signedTransactionHex?: string): void {
|
finishFlow(signedTransactionHex?: string): void {
|
||||||
|
|
|
@ -3,8 +3,7 @@ import {HttpClient} from '@angular/common/http';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {catchError, map} from 'rxjs/operators';
|
import {catchError, map} from 'rxjs/operators';
|
||||||
import {environment} from '../environments/environment';
|
import {environment} from '../environments/environment';
|
||||||
import {CryptoService} from './crypto.service';
|
import {AccountService} from './account.service';
|
||||||
import {GlobalVarsService} from './global-vars.service';
|
|
||||||
|
|
||||||
export class ProfileEntryResponse {
|
export class ProfileEntryResponse {
|
||||||
Username: string | null = null;
|
Username: string | null = null;
|
||||||
|
@ -25,8 +24,7 @@ export class BackendAPIService {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private cryptoService: CryptoService,
|
private accountService: AccountService,
|
||||||
private globalVars: GlobalVarsService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
post(path: string, body: any): Observable<any> {
|
post(path: string, body: any): Observable<any> {
|
||||||
|
@ -80,7 +78,7 @@ export class BackendAPIService {
|
||||||
): Observable<{bodyJson: string, signature: string} | null> {
|
): Observable<{bodyJson: string, signature: string} | null> {
|
||||||
|
|
||||||
// A stub for now
|
// A stub for now
|
||||||
const wallet : object | null = this.cryptoService.getWallet(this.globalVars.hostname);
|
const wallet : object | null = this.accountService.getWallet();
|
||||||
if (wallet === null) {
|
if (wallet === null) {
|
||||||
return of(null)
|
return of(null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +1,32 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {ec as EC} from 'elliptic';
|
import {ec as EC} from 'elliptic';
|
||||||
import bs58check from 'bs58check';
|
import bs58check from 'bs58check';
|
||||||
import {CookieService} from 'ngx-cookie';
|
|
||||||
import {createHmac, createCipher, createDecipher, randomBytes} from 'crypto';
|
import {createHmac, createCipher, createDecipher, randomBytes} from 'crypto';
|
||||||
import {AccessLevel, PrivateAccountInfo} from '../types/identity';
|
import {AccessLevel} from '../types/identity';
|
||||||
import { GlobalVarsService } from './global-vars.service';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class CryptoService {
|
export class CryptoService {
|
||||||
|
|
||||||
constructor(
|
constructor() {}
|
||||||
private cookieService: CookieService,
|
|
||||||
private globalVars: GlobalVarsService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// Safari only lets us store things in cookies
|
|
||||||
mustUseStorageAccess(): boolean {
|
|
||||||
// Webviews have full control over storage access
|
|
||||||
if (this.globalVars.webview) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const supportsStorageAccess = typeof document.hasStorageAccess === 'function';
|
|
||||||
const isChrome = navigator.userAgent.indexOf('Chrome') > -1;
|
|
||||||
const isSafari = !isChrome && navigator.userAgent.indexOf('Safari') > -1;
|
|
||||||
|
|
||||||
// Firefox and Edge support the storage access API but do not enforce it.
|
|
||||||
// For now, only use cookies if we support storage access and use Safari.
|
|
||||||
const mustUseStorageAccess = supportsStorageAccess && isSafari;
|
|
||||||
|
|
||||||
return mustUseStorageAccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 32 bytes = 256 bits is plenty of entropy for encryption
|
// 32 bytes = 256 bits is plenty of entropy for encryption
|
||||||
newEncryptionKey(): string {
|
newEncryptionKey(): string {
|
||||||
return randomBytes(32).toString('hex');
|
return randomBytes(32).toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO we won't need this soon right?
|
||||||
seedHexEncryptionStorageKey(hostname: string): string {
|
seedHexEncryptionStorageKey(hostname: string): string {
|
||||||
return `seed-hex-key-${hostname}`;
|
return `seed-hex-key-${hostname}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternate plan is to use this same system, but instead of encrypting the
|
|
||||||
// seed and sending it back and forth, we do the wallet. It may be superior
|
|
||||||
// because at most times the decrypted wallet is not accessible anywhere
|
|
||||||
// without sending a message between the app and identity service. But we'd
|
|
||||||
// have to trust that we'll never accidentally send the wallet to the app
|
|
||||||
// unencrypted.
|
|
||||||
walletStorageKey(hostname: string): string {
|
|
||||||
return `wallet-key-${hostname}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasWallet(hostname: string): boolean {
|
|
||||||
const storageKey = this.walletStorageKey(hostname);
|
|
||||||
|
|
||||||
if (this.mustUseStorageAccess()) {
|
|
||||||
return !!this.cookieService.get(storageKey);
|
|
||||||
} else {
|
|
||||||
return !!localStorage.getItem(storageKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasSeedHexEncryptionKey(hostname: string): boolean {
|
hasSeedHexEncryptionKey(hostname: string): boolean {
|
||||||
const storageKey = this.seedHexEncryptionStorageKey(hostname);
|
const storageKey = this.seedHexEncryptionStorageKey(hostname);
|
||||||
|
|
||||||
if (this.mustUseStorageAccess()) {
|
return !!localStorage.getItem(storageKey);
|
||||||
return !!this.cookieService.get(storageKey);
|
|
||||||
} else {
|
|
||||||
return !!localStorage.getItem(storageKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO define a wallet type, and/or use a type defined by json-schema
|
|
||||||
getWallet(hostname: string): {accounts: [PrivateAccountInfo]} | null {
|
|
||||||
const storageKey = this.walletStorageKey(hostname);
|
|
||||||
let walletStr
|
|
||||||
if (this.mustUseStorageAccess()) {
|
|
||||||
walletStr = this.cookieService.get(storageKey);
|
|
||||||
} else {
|
|
||||||
walletStr = localStorage.getItem(storageKey);
|
|
||||||
}
|
|
||||||
return JSON.parse(walletStr || 'null')
|
|
||||||
}
|
|
||||||
|
|
||||||
putWallet(hostname: string, wallet: object | null) {
|
|
||||||
const storageKey = this.walletStorageKey(hostname);
|
|
||||||
|
|
||||||
if (this.mustUseStorageAccess()) {
|
|
||||||
this.cookieService.put(storageKey, JSON.stringify(wallet), {
|
|
||||||
expires: new Date('2100/01/01 00:00:00'),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
localStorage.setItem(storageKey, JSON.stringify(wallet));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place a seed encryption key in storage. If reset is set to true, the
|
// Place a seed encryption key in storage. If reset is set to true, the
|
||||||
// previous key is overwritten, which is useful in logging out users.
|
// previous key is overwritten, which is useful in logging out users.
|
||||||
|
@ -103,20 +34,10 @@ export class CryptoService {
|
||||||
const storageKey = this.seedHexEncryptionStorageKey(hostname);
|
const storageKey = this.seedHexEncryptionStorageKey(hostname);
|
||||||
let encryptionKey;
|
let encryptionKey;
|
||||||
|
|
||||||
if (this.mustUseStorageAccess()) {
|
encryptionKey = localStorage.getItem(storageKey) || '';
|
||||||
encryptionKey = this.cookieService.get(storageKey);
|
if (!encryptionKey || reset) {
|
||||||
if (!encryptionKey || reset) {
|
encryptionKey = this.newEncryptionKey();
|
||||||
encryptionKey = this.newEncryptionKey();
|
localStorage.setItem(storageKey, encryptionKey);
|
||||||
this.cookieService.put(storageKey, encryptionKey, {
|
|
||||||
expires: new Date('2100/01/01 00:00:00'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
encryptionKey = localStorage.getItem(storageKey) || '';
|
|
||||||
if (!encryptionKey || reset) {
|
|
||||||
encryptionKey = this.newEncryptionKey();
|
|
||||||
localStorage.setItem(storageKey, encryptionKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the encryption key is unset or malformed we need to stop
|
// If the encryption key is unset or malformed we need to stop
|
||||||
|
|
16
src/app/hub.service.spec.ts
Normal file
16
src/app/hub.service.spec.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HubService } from './account.service';
|
||||||
|
|
||||||
|
describe('HubService', () => {
|
||||||
|
let service: HubService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(HubService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
29
src/app/hub.service.ts
Normal file
29
src/app/hub.service.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class HubService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
) { }
|
||||||
|
|
||||||
|
// Just for stubbing until we get the real thing
|
||||||
|
private rndStr() {
|
||||||
|
return (Math.random() + 1).toString(16).substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obviously just a stub for the actual API
|
||||||
|
public getChannels(xPubs: string[]): any {
|
||||||
|
return [{
|
||||||
|
claimId: this.rndStr(),
|
||||||
|
handle: '@test-' + this.rndStr(),
|
||||||
|
pubKeyAddress: this.rndStr(),
|
||||||
|
}, {
|
||||||
|
claimId: this.rndStr(),
|
||||||
|
handle: '@test-' + this.rndStr(),
|
||||||
|
pubKeyAddress: this.rndStr(),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import {v4 as uuid} from 'uuid';
|
||||||
import {AccessLevel, PublicChannelInfo} from '../types/identity';
|
import {AccessLevel, PublicChannelInfo} 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 {SigningService} from './signing.service';
|
import {SigningService} from './signing.service';
|
||||||
import {HttpParams} from '@angular/common/http';
|
import {HttpParams} from '@angular/common/http';
|
||||||
|
|
||||||
|
@ -24,12 +23,30 @@ export class IdentityService {
|
||||||
constructor(
|
constructor(
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private globalVars: GlobalVarsService,
|
private globalVars: GlobalVarsService,
|
||||||
private cookieService: CookieService,
|
|
||||||
private signingService: SigningService,
|
private signingService: SigningService,
|
||||||
) {
|
) {
|
||||||
window.addEventListener('message', (event) => this.handleMessage(event));
|
window.addEventListener('message', (event) => this.handleMessage(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safari only lets us store things in cookies
|
||||||
|
mustUseStorageAccess(): boolean {
|
||||||
|
// Webviews have full control over storage access
|
||||||
|
// TODO why do we trust the app to send this properly
|
||||||
|
if (this.globalVars.webview) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportsStorageAccess = typeof document.hasStorageAccess === 'function';
|
||||||
|
const isChrome = navigator.userAgent.indexOf('Chrome') > -1;
|
||||||
|
const isSafari = !isChrome && navigator.userAgent.indexOf('Safari') > -1;
|
||||||
|
|
||||||
|
// Firefox and Edge support the storage access API but do not enforce it.
|
||||||
|
// For now, only use cookies if we support storage access and use Safari.
|
||||||
|
const mustUseStorageAccess = supportsStorageAccess && isSafari;
|
||||||
|
|
||||||
|
return mustUseStorageAccess;
|
||||||
|
}
|
||||||
|
|
||||||
// Outgoing Messages
|
// Outgoing Messages
|
||||||
|
|
||||||
initialize(): Observable<any> {
|
initialize(): Observable<any> {
|
||||||
|
@ -41,8 +58,7 @@ export class IdentityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
login(payload: {
|
login(payload: {
|
||||||
channels: {[key: string]: PublicChannelInfo},
|
channel: PublicChannelInfo | null,
|
||||||
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[],
|
||||||
|
@ -118,7 +134,7 @@ export class IdentityService {
|
||||||
private async handleInfo(event: MessageEvent): Promise<void> {
|
private async handleInfo(event: MessageEvent): Promise<void> {
|
||||||
// check storage access API
|
// check storage access API
|
||||||
let hasStorageAccess = true;
|
let hasStorageAccess = true;
|
||||||
if (this.cryptoService.mustUseStorageAccess()) {
|
if (this.mustUseStorageAccess()) {
|
||||||
hasStorageAccess = await document.hasStorageAccess();
|
hasStorageAccess = await document.hasStorageAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,15 +146,37 @@ export class IdentityService {
|
||||||
hasLocalStorageAccess = false;
|
hasLocalStorageAccess = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for cookie access
|
// TODO - Sort out the storage access issue:
|
||||||
this.cookieService.put('deso-test-access', 'true');
|
//
|
||||||
const hasCookieAccess = !!this.cookieService.get('deso-test-access');
|
// There was a part of the code that was defaulting to cookies if
|
||||||
|
// this.mustUseStorageAccess() was true (only applies to Safari). I don't
|
||||||
|
// want to do that because I'm afraid the data we'll be storing won't fit.
|
||||||
|
// So, I'm going to consider browsers with this.mustUseStorageAccess() as
|
||||||
|
// unsupported until we can get back and figure out why DeSo chose to go
|
||||||
|
// with cookies for those cases.
|
||||||
|
//
|
||||||
|
// It looks like the issue is that Safari only supports saving in cookies
|
||||||
|
// (per the comment above this.mustUseStorageAccess(), which I moved from
|
||||||
|
// DeSo's crypto.service). If that's the case, I don't understand how it
|
||||||
|
// was able to save users and levels in the identity service. Someone who
|
||||||
|
// understands should look into that.
|
||||||
|
//
|
||||||
|
// PERHAPS it's that DeSo only strictly need certain things to be in
|
||||||
|
// localStorage, and everything else (like the encryption key) could fit
|
||||||
|
// in the cookie. Maybe we could store the wallet sync password in the
|
||||||
|
// cookie and reconstruct the wallet and channels on start. The problem
|
||||||
|
// would be resetting the access levels each time. Maybe that will just
|
||||||
|
// be the cost of using Safari.
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// https://github.com/deso-protocol/identity/blob/0543c40cb4e7e39cc9098554f99c27649e3d1d03/src/app/crypto.service.ts#L36
|
||||||
|
// https://github.com/deso-protocol/identity/blob/0543c40cb4e7e39cc9098554f99c27649e3d1d03/src/app/identity.service.ts#L261
|
||||||
|
// https://github.com/deso-protocol/identity/pull/50/
|
||||||
|
|
||||||
// store if browser is supported or not
|
// store if browser is supported or not
|
||||||
this.browserSupported = hasCookieAccess || hasLocalStorageAccess;
|
this.browserSupported = hasLocalStorageAccess && !this.mustUseStorageAccess();
|
||||||
|
|
||||||
this.respond(event.data.id, {
|
this.respond(event.data.id, {
|
||||||
hasCookieAccess,
|
|
||||||
hasStorageAccess,
|
hasStorageAccess,
|
||||||
hasLocalStorageAccess,
|
hasLocalStorageAccess,
|
||||||
browserSupported: this.browserSupported,
|
browserSupported: this.browserSupported,
|
||||||
|
|
|
@ -13,15 +13,14 @@ TODO - Unused for now. Revamp this page to be a channel picker for logging in
|
||||||
Wallet Sync Login redirects to here, like load-seed did. Now we choose which account to log in with.
|
Wallet Sync Login redirects to here, like load-seed did. Now we choose which account to log in with.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="d-flex flex-column" *ngIf="allAccountNames.length">
|
<div class="d-flex flex-column" *ngIf="hasChannels">
|
||||||
<ul class="list-group mt-7px mb-30px saved-seeds-list">
|
<ul class="list-group mt-7px mb-30px saved-seeds-list">
|
||||||
<span class="saved-seeds-header d-flex align-items-center"><span>Select an account</span></span>
|
<span class="saved-seeds-header d-flex align-items-center"><span>Select an account</span></span>
|
||||||
<div class="saved-seeds-scroll">
|
<div class="saved-seeds-scroll">
|
||||||
<li *ngFor="let item of allAccountNames | keyvalue" class="list-group-item list-group-item-action cursor-pointer saved-seed" (click)="selectAccount(item.key)">
|
<li *ngFor="let item of allChannels | keyvalue" class="list-group-item list-group-item-action cursor-pointer saved-seed" (click)="selectAccount(item.key)">
|
||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
<div *ngIf="!item.value" class="text-truncate">{{ item.key }}…</div>
|
|
||||||
<div *ngIf="item.value" class="d-flex align-items-center">
|
<div *ngIf="item.value" class="d-flex align-items-center">
|
||||||
<div class="text-truncate">{{ item.value }}</div>
|
<div class="text-truncate">{{ item.value.handle }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {Component, OnInit} from '@angular/core';
|
||||||
import {AccountService} from '../account.service';
|
import {AccountService} from '../account.service';
|
||||||
import {IdentityService} from '../identity.service';
|
import {IdentityService} from '../identity.service';
|
||||||
import {GlobalVarsService} from '../global-vars.service';
|
import {GlobalVarsService} from '../global-vars.service';
|
||||||
|
import {PublicChannelInfo} from '../../types/identity';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-log-in-app',
|
selector: 'app-log-in-app',
|
||||||
|
@ -9,7 +10,8 @@ import {GlobalVarsService} from '../global-vars.service';
|
||||||
styleUrls: ['./log-in-app.component.scss']
|
styleUrls: ['./log-in-app.component.scss']
|
||||||
})
|
})
|
||||||
export class LogInAppComponent implements OnInit {
|
export class LogInAppComponent implements OnInit {
|
||||||
allAccountNames: {[key: string]: string} = {};
|
allChannels: {[key: string]: PublicChannelInfo} = {};
|
||||||
|
hasChannels: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
|
@ -18,15 +20,15 @@ export class LogInAppComponent implements OnInit {
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// TODO - Will hopefully be channel claim IDs but I don't know what will be available in reality
|
this.allChannels = this.accountService.getChannels()
|
||||||
// this.allAccountNames = ...
|
this.hasChannels = Object.keys(this.allChannels).length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAccount(accountName: string): void {
|
selectAccount(channelClaimId: string): void {
|
||||||
this.accountService.setAccessLevel(accountName, this.globalVars.hostname, this.globalVars.accessLevelRequest);
|
this.accountService.setAccessCurrentChannel(this.globalVars.hostname, channelClaimId)
|
||||||
|
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
channels: this.accountService.getChannels(),
|
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
|
||||||
accountNameAdded: accountName,
|
|
||||||
signedUp: false
|
signedUp: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {BackendAPIService} from '../backend-api.service';
|
import {BackendAPIService} from '../backend-api.service';
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {AccountService} from '../account.service';
|
||||||
import {CryptoService} from '../crypto.service';
|
import {CryptoService} from '../crypto.service';
|
||||||
import {GlobalVarsService} from '../global-vars.service';
|
import {GlobalVarsService} from '../global-vars.service';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
|
@ -25,6 +26,7 @@ export class LogInWalletComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
private backendApi: BackendAPIService,
|
private backendApi: BackendAPIService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
|
private accountService: AccountService,
|
||||||
public globalVars: GlobalVarsService,
|
public globalVars: GlobalVarsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
) { }
|
) { }
|
||||||
|
@ -73,7 +75,7 @@ export class LogInWalletComponent implements OnInit {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cryptoService.putWallet(this.globalVars.hostname, wallet);
|
this.accountService.walletLogin(wallet);
|
||||||
|
|
||||||
// Clear the form
|
// Clear the form
|
||||||
this.loginUsername = '';
|
this.loginUsername = '';
|
||||||
|
@ -90,18 +92,16 @@ export class LogInWalletComponent implements OnInit {
|
||||||
// Paste Wallet (temporary measure for initial version)
|
// Paste Wallet (temporary measure for initial version)
|
||||||
|
|
||||||
getWalletDumpInitial() {
|
getWalletDumpInitial() {
|
||||||
const wallet : object | null = this.cryptoService.getWallet(this.globalVars.hostname);
|
const wallet : object | null = this.accountService.getWallet();
|
||||||
return JSON.stringify(wallet, null, 2) || ""
|
return JSON.stringify(wallet, null, 2) || ""
|
||||||
}
|
}
|
||||||
|
|
||||||
loginWithWalletDump(): void {
|
loginWithWalletDump(): void {
|
||||||
const walletStr = (<HTMLInputElement>document.getElementById("wallet-dump")).value;
|
const walletStr = (<HTMLInputElement>document.getElementById("wallet-dump")).value;
|
||||||
const wallet = JSON.parse(walletStr)
|
const wallet = JSON.parse(walletStr)
|
||||||
this.cryptoService.putWallet(this.globalVars.hostname, wallet);
|
this.accountService.walletLogin(wallet);
|
||||||
|
|
||||||
throw "fix me"
|
this.router.navigate(['/', RouteNames.LOG_IN_APP], {queryParamsHandling: 'merge'});
|
||||||
// For now, we'll just pick the first channel in the wallet and log right in
|
|
||||||
// this.router.navigate(['/', RouteNames.LOG_IN_APP], {queryParamsHandling: 'merge'});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,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.resetAccessLevels(this.globalVars.hostname);
|
this.accountService.appLogout(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.
|
||||||
|
@ -43,7 +43,7 @@ export class LogoutComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(): void {
|
finishFlow(): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
channels: this.accountService.getChannels(),
|
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ export class SignUpComponent implements OnInit, OnDestroy {
|
||||||
seedCopied = false;
|
seedCopied = false;
|
||||||
mnemonicCheck = '';
|
mnemonicCheck = '';
|
||||||
extraTextCheck = '';
|
extraTextCheck = '';
|
||||||
accountNameAdded = '';
|
|
||||||
|
|
||||||
// Advanced tab
|
// Advanced tab
|
||||||
showMnemonicError = false;
|
showMnemonicError = false;
|
||||||
|
@ -89,10 +88,10 @@ export class SignUpComponent implements OnInit, OnDestroy {
|
||||||
const extraText = this.extraTextCheck;
|
const extraText = this.extraTextCheck;
|
||||||
const keychain = this.cryptoService.mnemonicToKeychain(mnemonic, extraText);
|
const keychain = this.cryptoService.mnemonicToKeychain(mnemonic, extraText);
|
||||||
|
|
||||||
this.accountNameAdded = this.accountService.addUser(keychain, mnemonic, extraText, network);
|
const accountNameAdded = this.accountService.addUser(keychain, mnemonic, extraText, network);
|
||||||
|
|
||||||
this.accountService.setAccessLevel(
|
this.accountService.setAccessLevel(
|
||||||
this.accountNameAdded, this.globalVars.hostname, this.globalVars.accessLevelRequest);
|
accountNameAdded, this.globalVars.hostname, this.globalVars.accessLevelRequest);
|
||||||
|
|
||||||
this.login();
|
this.login();
|
||||||
*/
|
*/
|
||||||
|
@ -106,8 +105,7 @@ export class SignUpComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
login(): void {
|
login(): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
channels: this.accountService.getChannels(),
|
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
|
||||||
accountNameAdded: this.accountNameAdded,
|
|
||||||
signedUp: true,
|
signedUp: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ import {AccountService} from '../account.service';
|
||||||
import {Component, OnInit} from '@angular/core';
|
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 {GlobalVarsService} from '../global-vars.service';
|
||||||
import { GlobalVarsService } from '../global-vars.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-test-sign-transaction',
|
selector: 'app-test-sign-transaction',
|
||||||
|
@ -18,7 +17,6 @@ export class TestSignTransactionComponent implements OnInit {
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private signingService: SigningService,
|
private signingService: SigningService,
|
||||||
private identityService: IdentityService,
|
private identityService: IdentityService,
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private globalVars: GlobalVarsService,
|
private globalVars: GlobalVarsService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
@ -28,7 +26,7 @@ export class TestSignTransactionComponent implements OnInit {
|
||||||
const fromAddress = params.get("fromAddress") || ""
|
const fromAddress = params.get("fromAddress") || ""
|
||||||
const nonWitnessUtxoHexes = params.get("nonWitnessUtxoHexes") || null
|
const nonWitnessUtxoHexes = params.get("nonWitnessUtxoHexes") || null
|
||||||
|
|
||||||
const wallet : object | null = this.cryptoService.getWallet(this.globalVars.hostname);
|
const wallet : object | null = this.accountService.getWallet();
|
||||||
const signingKey = this.signingService.getSigningKey(wallet, fromAddress)
|
const signingKey = this.signingService.getSigningKey(wallet, fromAddress)
|
||||||
|
|
||||||
// TODO what if error? etc etc.
|
// TODO what if error? etc etc.
|
||||||
|
@ -41,7 +39,7 @@ export class TestSignTransactionComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(signedTransactionHex?: string): void {
|
finishFlow(signedTransactionHex?: string): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
channels: this.accountService.getChannels(),
|
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
|
||||||
signedTransactionHex,
|
signedTransactionHex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {AccountService} from '../account.service';
|
import {AccountService} from '../account.service';
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {GlobalVarsService} from '../global-vars.service';
|
||||||
import {SigningService} from '../signing.service';
|
import {SigningService} from '../signing.service';
|
||||||
import {IdentityService} from '../identity.service';
|
import {IdentityService} from '../identity.service';
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ export class TestSignComponent implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
|
private globalVars: GlobalVarsService,
|
||||||
private signingService: SigningService,
|
private signingService: SigningService,
|
||||||
private identityService: IdentityService,
|
private identityService: IdentityService,
|
||||||
) { }
|
) { }
|
||||||
|
@ -36,7 +38,7 @@ export class TestSignComponent implements OnInit {
|
||||||
|
|
||||||
finishFlow(signatureHex?: string): void {
|
finishFlow(signatureHex?: string): void {
|
||||||
this.identityService.login({
|
this.identityService.login({
|
||||||
channels: this.accountService.getChannels(),
|
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
|
||||||
signatureHex,
|
signatureHex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,19 @@ export interface PrivateAccountInfo {
|
||||||
// can be sent to the app
|
// can be sent to the app
|
||||||
export interface PublicChannelInfo {
|
export interface PublicChannelInfo {
|
||||||
// TODO - add more useful stuff
|
// TODO - add more useful stuff
|
||||||
|
claimId: string;
|
||||||
|
handle: string;
|
||||||
pubKeyAddress: string;
|
pubKeyAddress: string;
|
||||||
network: Network;
|
|
||||||
accessLevel: AccessLevel;
|
// Don't care about sending the hmac-verifiable accessLevel to the app for it
|
||||||
accessLevelHmac: string;
|
// to send back, as DeSo did. I don't get it, it's overly complicated. We can
|
||||||
|
// just check the permissions based on what's in localStorage.
|
||||||
|
//
|
||||||
|
// Though, maybe this was for the sake of Safari where localStorage doesn't
|
||||||
|
// work? We'll see I guess.
|
||||||
|
//
|
||||||
|
// accessLevel: AccessLevel;
|
||||||
|
// accessLevelHmac: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AccessLevel {
|
export enum AccessLevel {
|
||||||
|
@ -60,3 +69,9 @@ export enum AccessLevel {
|
||||||
// Node can sign all transactions without approval
|
// Node can sign all transactions without approval
|
||||||
Full = 4,
|
Full = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ActionType {
|
||||||
|
Action = 0,
|
||||||
|
Transaction = 1,
|
||||||
|
// TODO - probably gets a lot more detailed than this
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue