Compare commits

..

44 commits

Author SHA1 Message Date
Daniel Krol
fd74cf1f5a WIP - Channels, spending Keys (sort of) from hub
This doesn't compile. Lots of TODO comments and such. This should be undone and force pushed before merging into master.
2022-05-24 15:39:02 -04:00
Daniel Krol
8ad085c24c Get rid of a use of a constant that I don't need.
Need to delete a use of it from one other place and I can delete it altogether.

Some comments as well on related stuff.
2022-05-23 18:02:42 -04:00
Daniel Krol
f841c897dc Comment and cleanup TODO 2022-05-23 17:10:18 -04:00
Daniel Krol
1c8fc79f08 Comment about code cleanup later 2022-05-23 16:56:13 -04:00
Daniel Krol
1dcb1d36a1 Better localStorage format description 2022-05-23 16:25:30 -04:00
Daniel Krol
30c17f3bdb Upgrade bitcoinjs-lib (lbry fork) package 2022-05-23 14:55:30 -04:00
Daniel Krol
e7c61254a5 Choose a random hub 2022-05-10 17:36:30 -04:00
Daniel Krol
5acc79d212 Clean up app login page. Remove unused global var. 2022-05-10 16:06:18 -04:00
Daniel Krol
a913d45060 Missing imports and environment var for hub 2022-05-10 15:24:54 -04:00
Daniel Krol
14e9f4d925 Forgot one for accounts->channel update 2022-05-10 15:23:08 -04:00
Daniel Krol
6bbd324388 Uninstall ngx-cookie and ngx-intl-tel-input
Unused anymore
2022-05-10 15:13:54 -04:00
Daniel Krol
d0b197c5b0 Begin to get Hub service ready for http connections 2022-05-10 14:59:39 -04:00
Daniel Krol
1c7854b9ec 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.
2022-05-10 12:57:11 -04:00
Daniel Krol
d0583e540a getPublicAccounts -> getChannels
Login won't give LBRY accounts, it'll give channels
2022-05-05 19:49:29 -04:00
Daniel Krol
439ce26e0f Renamed components for clarity
test-lbry-log-in -> log-in-wallet
log-in -> log-in-app
2022-05-05 16:55:00 -04:00
Daniel Krol
65d675041a WIP for the new "log in" page 2022-05-05 16:08:59 -04:00
Daniel Krol
4e891a5763 Comment and throw in weird place in code 2022-05-05 15:11:41 -04:00
Daniel Krol
c69894806c Update but also disable sign-up component.
We're not ready yet and certainly not using DeSo's.
2022-05-05 13:53:00 -04:00
Daniel Krol
340e6ccaa3 Rename a method and make it private. Comment it out because we don't need it yet. 2022-05-05 13:49:22 -04:00
Daniel Krol
b18af383be Add LBRY to LICENSE notice 2022-05-05 13:45:55 -04:00
Daniel Krol
aee6b977d7 TODO - example READMEs 2022-05-05 13:39:25 -04:00
Daniel Krol
205411adda A bit of half-backed wallet sync stuff, but commented out. 2022-05-05 13:30:07 -04:00
Daniel Krol
dadeb066c8 Some comments 2022-05-04 19:44:16 -04:00
Daniel Krol
970c0e68e7 Remove some stuff we wont need in signing and identity service 2022-05-04 19:13:44 -04:00
Daniel Krol
a761ba1a06 Remove jwtPost from backend-api in identity service 2022-05-04 18:14:46 -04:00
Daniel Krol
95d783ffc1 Remove protractor (testing) related stuff we're most likely not going to use 2022-05-04 17:34:54 -04:00
Daniel Krol
a3023dc40c Very WIP changes from DeSo structs to LBRY structs 2022-05-04 15:58:21 -04:00
Daniel Krol
6b6efe6ce0 Disable DeSo's approve component. TODOs to put in our own txn signing stuff 2022-05-04 15:23:14 -04:00
Daniel Krol
5c8d42b2b1 Get rid of PUBLIC_KEY_PREFIXES
I think we have all we need in bitcoinjs-lib
2022-05-04 15:09:00 -04:00
Daniel Krol
ee4db066a0 Remove load-seed component
We won't be "loading a seed". We'll be doing a wallet service login in the long run, pasting a wallet in the interim.
2022-05-04 14:04:10 -04:00
Daniel Krol
eecf21aec4 Get rid of some functions we won't need soon (this won't compile)
This and the next few commits won't be clean; I made a bunch of changes and I need to start committing in chunks.
2022-05-04 12:05:41 -04:00
Daniel Krol
2e84e0ff40 Remove parameter we don't care about. 2022-04-09 19:18:27 -04:00
Daniel Krol
a75c7b22a1 Get rid of a few more fields 2022-04-09 19:06:29 -04:00
Daniel Krol
51e089f9a9 Don't need UserProfile struct anymore. We'll make a new one if this separation matters again. 2022-04-09 19:04:23 -04:00
Daniel Krol
44f65ab93e Space 2022-04-09 18:00:03 -04:00
Daniel Krol
b89ff006ce Remove DeSo's GetTransactionSpending. LBRY will get this info from parsing psbts. 2022-04-09 17:38:19 -04:00
Daniel Krol
8441a49232 Don't need stringifyError 2022-04-09 16:48:46 -04:00
Daniel Krol
8b28bd35ff Delete a bunhc of environment vars we don't care about. 2022-04-09 16:07:30 -04:00
Daniel Krol
a9b3a683c4 uninstall amplitude-js 2022-04-09 16:04:23 -04:00
Daniel Krol
0c76472093 Delete extra LICENSE. It's the same entity and year as the other one anyway anyway. 2022-04-09 15:41:06 -04:00
Daniel Krol
14d36075fc Delete a bunch of angular/deso stuff we won't need post-angular 2022-04-09 15:39:29 -04:00
Daniel Krol
7cd0eee93b Remove reference to bitclout and a couple other things 2022-04-09 15:14:15 -04:00
Daniel Krol
b7fc4f9e8e Some hostnames in environment 2022-04-09 14:51:29 -04:00
Daniel Krol
af4bbc9949 package-lock.json reference reformat 2022-04-08 20:35:46 -04:00
81 changed files with 1285 additions and 1751 deletions

View file

@ -1,2 +0,0 @@
# For now, tag everyone who wants to participate
* @deso-protocol/reviewers

View file

@ -1,46 +0,0 @@
# Global caddy config options must be first
{
admin off
auto_https off
}
# Bind to port 82
:82
# Serve static files
file_server
# Fallback to index.html for everything but assets
@html {
not path *.js *.css *.png *.svg *.ttf *.woff2
file index.html
}
handle_errors {
header Cache-Control no-store
}
rewrite @html {http.matchers.file.relative}
# Don't cache index.html and set CSP
header @html Cache-Control no-store
header @html Content-Security-Policy "
default-src 'self';
connect-src
{$DOMAIN:https://node.deso.org}/api/v0/get-users-stateless
{$DOMAIN:https://node.deso.org}/api/v0/get-app-state
{$DOMAIN:https://node.deso.org}/api/v0/get-referral-info-for-referral-hash
{$DOMAIN:https://node.deso.org}/api/v0/get-user-derived-keys
{$DOMAIN:https://node.deso.org}/api/v0/get-transaction-spending
{$DOMAIN:https://node.deso.org}/api/v0/send-phone-number-verification-text
{$DOMAIN:https://node.deso.org}/api/v0/submit-phone-number-verification-code
img-src 'self'
{$DOMAIN:https://node.deso.org}/api/v0/get-single-profile-picture/;
style-src 'self' 'unsafe-inline'
https://fonts.googleapis.com
https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css;
font-src 'self'
https://fonts.googleapis.com
https://fonts.gstatic.com
https://ka-f.fontawesome.com;"

View file

@ -1,43 +0,0 @@
FROM node:14.15.5-alpine3.13 AS identity
WORKDIR /identity
RUN apk add git
COPY ./package.json .
COPY ./package-lock.json .
COPY ./.npmrc .
# use yarn to upgrade npm
RUN yarn global add npm@7
# install frontend dependencies before copying the frontend code
# into the container so we get docker cache benefits
RUN npm install
# don't allow any dependencies with vulnerabilities
#RUN npx audit-ci --low
# running ngcc before build_prod lets us utilize the docker
# cache and significantly speeds up builds without requiring us
# to import/export the node_modules folder from the container
RUN npm run ngcc
COPY ./angular.json .
COPY ./tsconfig.json .
COPY ./tsconfig.app.json .
COPY ./webpack.config.js .
COPY ./tslint.json .
COPY ./src ./src
RUN npm run build_prod
# build minified version of frontend, served using caddy
FROM caddy:2.3.0-alpine
WORKDIR /identity
COPY ./Caddyfile .
COPY --from=identity /identity/dist/identity .
ENTRYPOINT ["caddy", "run"]

View file

@ -1,6 +1,7 @@
MIT License
Copyright (c) 2021 DeSo Community Developers
Copyright (c) 2022 LBRY
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -88,23 +88,6 @@
"browserTarget": "identity:build"
}
},
"test": {
"builder": "@angular-bulders/custom-webpack:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {

View file

@ -1,37 +0,0 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};

View file

@ -1,23 +0,0 @@
import { browser, logging } from 'protractor';
import { AppPage } from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', async () => {
await page.navigateTo();
expect(await page.getTitleText()).toEqual('identity app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View file

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
async navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl);
}
async getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText();
}
}

View file

@ -1,13 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"node"
]
}
}

View file

@ -0,0 +1 @@
TODO: explain config.go

View file

@ -1,2 +0,0 @@
# For now, tag everyone who wants to participate
* @deso-protocol/reviewers

View file

@ -1,92 +0,0 @@
# Global caddy config options must be first
{
admin off
auto_https off
}
# Bind to port 80
:80
# Serve static files
file_server
# Fallback to index.html for everything but assets
@html {
not path *.js *.css *.png *.jpg *.svg *.pdf *.eot *.ttf *.woff *.woff2 *.webmanifest
file index.html
}
handle_errors {
header Cache-Control no-store
}
rewrite @html {http.matchers.file.relative}
# Don't cache index.html and set CSP
header @html Cache-Control no-store
header @html Content-Security-Policy "
default-src 'self';
connect-src 'self'
node.deso.org
amp.deso.org
bithunt.deso.org
bitclout.com:*
api.bitclout.com
bithunt.bitclout.com
https://altumbase.com
localhost:*
explorer.bitclout.com
https://api.blockchain.com/ticker
https://api.blockchain.com/mempool/fees
https://ka-f.fontawesome.com/
bitcoinfees.earn.com
api.blockcypher.com
amp.bitclout.com
api.testwyre.com
api.sendwyre.com
https://videodelivery.net
https://upload.videodelivery.net;
script-src 'self'
https://kit.fontawesome.com/070ca4195b.js
https://ka-f.fontawesome.com/;
style-src 'self'
'unsafe-inline'
https://fonts.googleapis.com
https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css;
img-src 'self'
data:
i.imgur.com
images.deso.org
images.bitclout.com
quickchart.io
arweave.net
*.arweave.net
*.pearl.app
cloudflare-ipfs.com;
font-src 'self'
https://fonts.googleapis.com
https://fonts.gstatic.com
https://ka-f.fontawesome.com;
frame-src 'self'
localhost:*
identity.deso.org
identity.deso.blue
identity.deso.green
identity.bitclout.com
identity.bitclout.blue
identity.bitclout.green
https://geo.captcha-delivery.com
https://www.youtube.com
https://youtube.com
https://player.vimeo.com
https://www.tiktok.com
https://giphy.com
https://open.spotify.com
https://w.soundcloud.com
https://player.twitch.tv
https://clips.twitch.tv
pay.testwyre.com
pay.sendwyre.com
https://iframe.videodelivery.net;
frame-ancestors 'self';"

View file

@ -1,60 +0,0 @@
FROM node:14.15.5-alpine3.13 AS frontend
WORKDIR /frontend
# install git
RUN apk add git
# use yarn to upgrade npm
RUN yarn global add npm@7
COPY ./package.json .
COPY ./package-lock.json .
COPY ./.npmrc .
# install frontend dependencies before copying the frontend code
# into the container so we get docker cache benefits
RUN npm install
# don't allow any dependencies with vulnerabilities
#RUN npx audit-ci --low
# running ngcc before build_prod lets us utilize the docker
# cache and significantly speeds up builds without requiring us
# to import/export the node_modules folder from the container
RUN npm run ngcc
COPY ./angular.json .
COPY ./tsconfig.json .
COPY ./src ./src
# use --build-arg index=index.custom.html to specify a custom index.html file
ARG index=index.html
# overwrite default index file with custom file
COPY ./src/$index ./src/index.html
# use --build-arg environment=custom to specify a custom environment
ARG environment=prod
# overwrite default environment file with custom file
COPY ./src/environments/environment.$environment.ts ./src/environments/environment.prod.ts
RUN npm run build_prod
# build minified version of frontend, served using caddy
FROM caddy:2.3.0-alpine
WORKDIR /frontend
COPY ./Caddyfile .
COPY --from=frontend /frontend/dist .
# We use a run.sh script so that we can pass environment variables
# to it.
COPY ./run.sh .
# Default options overrideable by docker-compose
ENV CADDY_FILE "/frontend/Caddyfile"
ENTRYPOINT ["/frontend/run.sh"]

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 DeSo Community Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,34 +1 @@
![DeSo Logo](src/assets/deso/camelcase_logo.svg)
# About DeSo
DeSo is a blockchain built from the ground up to support a fully-featured
social network. Its architecture is similar to Bitcoin, only it supports complex
social network data like profiles, posts, follows, creator coin transactions, and
more.
[Read about the vision](https://docs.deso.org/#the-ultimate-vision)
# About This Repo
Documentation for this repo lives on docs.deso.org. Specifically, the following
docs should give you everything you need to get started:
* [DeSo Code Walkthrough](https://docs.deso.org/code/walkthrough)
* [Setting Up Your Dev Environment](https://docs.deso.org/code/dev-setup)
* [Making Your First Changes](https://docs.deso.org/code/making-your-first-changes)
# Start Coding
The quickest way to contribute changes to the BitClout Frontend is the following these steps:
1. Open frontend repo in Gitpod
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/deso-protocol/frontend)
You can use any repo / branch URL and just prepend `https://gitpod.io/#` to it.
2. If needed, login to your github account
3. Set the correct `lastLocalNodeV2` to `"https://api.tijn.club"` in your browser Local Storage for the gitpod preview URL
4. Create a new branch to start working
To commit / submit a pull reqest from gitpod, you will need to give gitpod additional permissions to your github account: `public_repo, read:org, read:user, repo, user:email, workflow` which you can do on the [GitPod Integrations page](https://gitpod.io/integrations).
TODO

View file

@ -1,45 +0,0 @@
{
"extends": "../.eslintrc.json",
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"e2e//tsconfig.app.json",
"e2e//tsconfig.spec.json",
"e2e//e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"rules": {}
}
]
}

View file

@ -1,28 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View file

@ -1,25 +0,0 @@
import { AppPage } from "./app.po";
import { browser, logging } from "protractor";
describe("workspace-project App", () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it("should display welcome message", () => {
page.navigateTo();
expect(page.getTitleText()).toEqual("Welcome to electron-angular-app!");
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(
jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry)
);
});
});

View file

@ -1,11 +0,0 @@
import { browser, by, element } from "protractor";
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css("app-root h1")).getText() as Promise<string>;
}
}

View file

@ -1,13 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View file

@ -20,7 +20,6 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"amplitude-js": "^7.4.3",
"assert": "^2.0.0",
"autolinker": "^3.14.2",
"bs58": "^4.0.1",
@ -101,39 +100,6 @@
"webpack-cli": "^3.3.10"
}
},
"node_modules/@amplitude/types": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@amplitude/types/-/types-1.5.0.tgz",
"integrity": "sha512-XspuOsUzUcxwAptHeGiIn4giuLWs285xTJa7h8kAEEynxtEI3/krWCoDYZSB9PekaPXB6phxiO/tMd9t5V9LgQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@amplitude/ua-parser-js": {
"version": "0.7.24",
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.24.tgz",
"integrity": "sha512-VbQuJymJ20WEw0HtI2np7EdC3NJGUWi8+Xdbc7uk8WfMIF308T0howpzkQ3JFMN7ejnrcSM/OyNGveeE3TP3TA==",
"engines": {
"node": "*"
}
},
"node_modules/@amplitude/utils": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@amplitude/utils/-/utils-1.5.0.tgz",
"integrity": "sha512-1DrDJkb4dVX+FiBXhGpO2Dn2cRKdP+gtrVR8vZcE8wz/V2XxUI3DDx7uQbIS6WbQf6swv6Uo2eMHYtrwebostw==",
"dependencies": {
"@amplitude/types": "^1.5.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@amplitude/utils/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/@angular-devkit/architect": {
"version": "0.1002.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1002.1.tgz",
@ -5713,17 +5679,6 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true
},
"node_modules/amplitude-js": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-7.4.3.tgz",
"integrity": "sha512-ea42XGJ/4MwqtJ/OZX5uZf2VsNePONUNTJoEaaTxjCwFGk5sR5bOh9W60puMuL1HQ3YSy6lFBdrcY4wzaCl7PA==",
"dependencies": {
"@amplitude/ua-parser-js": "0.7.24",
"@amplitude/utils": "^1.0.5",
"blueimp-md5": "^2.10.0",
"query-string": "5"
}
},
"node_modules/ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
@ -6691,11 +6646,6 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"node_modules/blueimp-md5": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
"integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q=="
},
"node_modules/bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
@ -27902,32 +27852,6 @@
}
},
"dependencies": {
"@amplitude/types": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@amplitude/types/-/types-1.5.0.tgz",
"integrity": "sha512-XspuOsUzUcxwAptHeGiIn4giuLWs285xTJa7h8kAEEynxtEI3/krWCoDYZSB9PekaPXB6phxiO/tMd9t5V9LgQ=="
},
"@amplitude/ua-parser-js": {
"version": "0.7.24",
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.24.tgz",
"integrity": "sha512-VbQuJymJ20WEw0HtI2np7EdC3NJGUWi8+Xdbc7uk8WfMIF308T0howpzkQ3JFMN7ejnrcSM/OyNGveeE3TP3TA=="
},
"@amplitude/utils": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@amplitude/utils/-/utils-1.5.0.tgz",
"integrity": "sha512-1DrDJkb4dVX+FiBXhGpO2Dn2cRKdP+gtrVR8vZcE8wz/V2XxUI3DDx7uQbIS6WbQf6swv6Uo2eMHYtrwebostw==",
"requires": {
"@amplitude/types": "^1.5.0",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@angular-devkit/architect": {
"version": "0.1002.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1002.1.tgz",
@ -32184,17 +32108,6 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true
},
"amplitude-js": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-7.4.3.tgz",
"integrity": "sha512-ea42XGJ/4MwqtJ/OZX5uZf2VsNePONUNTJoEaaTxjCwFGk5sR5bOh9W60puMuL1HQ3YSy6lFBdrcY4wzaCl7PA==",
"requires": {
"@amplitude/ua-parser-js": "0.7.24",
"@amplitude/utils": "^1.0.5",
"blueimp-md5": "^2.10.0",
"query-string": "5"
}
},
"ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
@ -32928,11 +32841,6 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"blueimp-md5": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
"integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q=="
},
"bn.js": {
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",

View file

@ -29,7 +29,6 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"amplitude-js": "^7.4.3",
"assert": "^2.0.0",
"autolinker": "^3.14.2",
"bs58": "^4.0.1",

View file

@ -1,5 +0,0 @@
#!/bin/sh
echo "Loading Caddy config from file: ${CADDY_FILE}"
caddy run --config ${CADDY_FILE}

View file

@ -174,6 +174,8 @@ export class BackendApiService {
}
jwtPost(endpoint: string, path: string, publicKey: string, body: any): Observable<any> {
// TODO - we may not need this method
// see notes in: (identity) src/app/identity.service.ts
const request = this.identityService.jwt({
...this.identityService.identityServiceParamsForKey(publicKey),
});

View file

@ -73,7 +73,7 @@ export class GlobalVarsService {
testLoginLBRY() : Observable<string[]> {
return new Observable(subscriber => {
this.identityService.launch("/test-lbry-log-in", {}).subscribe((res) => {
this.identityService.launch("/log-in-wallet", {}).subscribe((res) => {
// TODO - maybe we want public key instead of address? we should, as DeSo did, have a list of users with everything we need from them.
subscriber.next(res.addresses)
subscriber.complete()

View file

@ -50,7 +50,6 @@ export class IdentityService {
nonWitnessUtxoHexes?: string,
fromAddress?: string,
public_key?: string;
accessLevelRequest?: number;
}
): Observable<any> {
let url = this.identityServiceURL as string;
@ -92,10 +91,6 @@ export class IdentityService {
httpParams = httpParams.append("public_key", params.public_key);
}
if (params?.accessLevelRequest) {
httpParams = httpParams.append("accessLevelRequest", params.accessLevelRequest.toString());
}
const paramsStr = httpParams.toString();
if (paramsStr) {
url += `?${paramsStr}`;

View file

@ -60,7 +60,7 @@ export class SpendLBCComponent implements OnInit {
const toAddress = (<HTMLInputElement>document.getElementById("toAddress")).value;
const desiredAmount = Number((<HTMLInputElement>document.getElementById("desiredAmount")).value);
this.backendApi.GetPsbt("localhost:8090", this.fromAddress, toAddress, desiredAmount).subscribe({
this.backendApi.GetPsbt(environment.backendHostname, this.fromAddress, toAddress, desiredAmount).subscribe({
next: res => {
this.psbtHex = res.psbtHex
this.nonWitnessUtxoHexes = res.nonWitnessUtxoHex
@ -99,14 +99,14 @@ export class SpendLBCComponent implements OnInit {
this.globalVars.testSignTransactionLBRY(this.psbtHex, this.nonWitnessUtxoHexes, this.fromAddress).subscribe({
next: (signedTransactionHex) => {
this.signedTransactionHex = signedTransactionHex
this.backendApi.DecodeTransaction("localhost:8090", signedTransactionHex).subscribe({
this.backendApi.DecodeTransaction(environment.backendHostname, signedTransactionHex).subscribe({
next: res => {
this.decodedTransaction = res.decodedTransaction
console.log(res.decodedTransaction)
},
error: err => { this.setError(err) }
})
this.backendApi.BroadcastTransaction("localhost:8090", signedTransactionHex).subscribe({
this.backendApi.BroadcastTransaction(environment.backendHostname, signedTransactionHex).subscribe({
next: res => {
this.setError(null)
this.success = res.txid

View file

@ -2,32 +2,7 @@
"name": "deso",
"short_name": "deso",
"description": "The crypto social network",
"icons": [
{
"src": "/assets/bitclout/logo-192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "maskable"
},
{
"src": "/assets/bitclout/logo-512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
},
{
"src": "/assets/bitclout/logo-192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "any"
},
{
"src": "/assets/bitclout/logo-512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "any"
}
],
"icons": [],
"start_url": "/",
"scope": "/",
"display": "standalone",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -1,7 +0,0 @@
<svg width="138" height="28" viewBox="0 0 138 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.7001 21.7632H35.7321C40.1781 21.7632 42.9601 20.5932 42.9601 16.9012C42.9601 13.6252 40.7501 12.4812 37.9421 12.4292V11.8052C39.8401 11.6752 41.6081 10.5832 41.6081 7.93118C41.6081 4.31718 39.0341 3.43318 34.5621 3.43318H29.7001V21.7632ZM31.8321 11.3112V5.25318H34.6401C37.6301 5.25318 39.4761 5.43518 39.4761 8.26918C39.4761 10.8692 37.6301 11.3112 34.6401 11.3112H31.8321ZM31.8321 19.9432V13.1312H35.8101C38.9301 13.1312 40.8281 13.8592 40.8281 16.5372C40.8281 19.4232 38.9301 19.9432 35.8101 19.9432H31.8321ZM49.2637 6.78718C50.3037 6.78718 51.1357 6.00718 51.1357 4.96718C51.1357 3.97918 50.3037 3.17318 49.2637 3.17318C48.1977 3.17318 47.3917 3.97918 47.3917 4.96718C47.3917 6.00718 48.1977 6.78718 49.2637 6.78718ZM44.9477 21.7632H54.2297V19.9432H50.5897V10.1412C50.5897 9.15318 50.1217 8.47718 48.8997 8.58118L45.9097 8.84118V10.6612L48.5877 10.4272V19.9432H44.9477V21.7632ZM59.29 20.2292V10.6612H63.32V8.84118H59.29V4.86318L57.184 5.51318V8.84118H54.324V10.6612H57.184V20.2552C57.184 21.4512 57.73 22.1012 58.952 22.0232L63.84 21.7632V19.9432L59.29 20.2292ZM66.612 12.5852C66.612 17.9412 69.81 22.0232 75.192 22.0232C79.872 22.0232 82.94 19.2152 83.486 14.6652H81.354C80.938 18.1752 78.78 20.2032 75.192 20.2032C70.928 20.2032 68.744 17.0312 68.744 12.5852C68.744 8.16518 70.928 4.99318 75.192 4.99318C78.78 4.99318 80.938 6.99518 81.354 10.5052H83.486C82.94 5.98118 79.872 3.17318 75.192 3.17318C69.81 3.17318 66.612 7.25518 66.612 12.5852ZM84.7033 21.7632H94.0633V19.9432H90.4493V4.18718C90.4493 2.96518 89.8773 2.28918 88.6813 2.39318L85.6653 2.65318V4.47318L88.3433 4.23918V19.9432H84.7033V21.7632ZM95.5258 15.2892C95.5258 19.0592 98.2298 22.0232 102.364 22.0232C106.472 22.0232 109.15 19.0592 109.15 15.2892C109.15 11.5452 106.472 8.58118 102.364 8.58118C98.2298 8.58118 95.5258 11.5452 95.5258 15.2892ZM97.6578 15.2892C97.6578 12.6112 99.2958 10.4012 102.364 10.4012C105.406 10.4012 107.044 12.6112 107.044 15.2892C107.044 17.9932 105.406 20.2032 102.364 20.2032C99.2958 20.2032 97.6578 17.9932 97.6578 15.2892ZM117.266 20.2032C115.03 20.2032 113.99 19.1632 113.99 16.4592V8.84118H111.858V16.7452C111.858 20.3332 113.756 22.0232 116.694 22.0232C119.502 22.0232 121.14 20.3852 121.66 18.0452H122.258L121.92 19.5272V21.7632H124.026V8.84118H121.92V14.5872C121.92 18.0452 119.996 20.2032 117.266 20.2032ZM131.247 20.2292V10.6612H135.277V8.84118H131.247V4.86318L129.141 5.51318V8.84118H126.281V10.6612H129.141V20.2552C129.141 21.4512 129.687 22.1012 130.909 22.0232L135.797 21.7632V19.9432L131.247 20.2292Z" fill="#222222"/>
<path d="M9.66243 25.3511L17.6755 15.2309C17.8052 15.067 17.6885 14.8257 17.4795 14.8257H0.907031C0.694218 14.8257 0.57872 15.0746 0.716149 15.2371L9.27554 25.3574C9.37753 25.478 9.56439 25.475 9.66243 25.3511Z" fill="white" stroke="black"/>
<path d="M17.5745 14.3587L9.44746 10.4872C9.30597 10.4197 9.14105 10.4226 9.00196 10.4948L0.999407 14.6513C0.650668 14.8324 0.637574 15.3265 0.976231 15.5259L8.96405 20.2278C9.1233 20.3216 9.32127 20.3198 9.47885 20.2233L17.6206 15.2365C17.9589 15.0293 17.9327 14.5294 17.5745 14.3587Z" fill="white" stroke="black"/>
<path d="M9.66243 15.971L17.6755 5.85075C17.8052 5.68686 17.6885 5.44556 17.4795 5.44556H0.907031C0.694218 5.44556 0.57872 5.69451 0.716149 5.857L9.27554 15.9773C9.37753 16.0979 9.56439 16.0948 9.66243 15.971Z" fill="white" stroke="black"/>
<path d="M17.5745 4.97862L9.44746 1.10703C9.30597 1.03962 9.14105 1.04246 9.00196 1.1147L0.999407 5.27113C0.650668 5.45226 0.637574 5.9464 0.976231 6.14574L8.96405 10.8477C9.1233 10.9414 9.32127 10.9397 9.47885 10.8432L17.6206 5.85639C17.9589 5.64915 17.9327 5.14926 17.5745 4.97862Z" fill="white" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="704pt" height="801pt" viewBox="0 0 704 801" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="#000000ff">
<path fill="#000000" opacity="1.00" d=" M 273.03 42.04 C 299.57 28.75 325.97 15.16 352.57 2.00 C 442.37 47.92 532.24 93.71 622.15 139.42 C 616.20 148.00 609.48 156.04 603.23 164.41 C 554.73 227.60 506.20 290.76 457.70 353.95 C 512.62 381.98 567.58 409.91 622.49 437.96 C 532.95 554.49 443.40 671.01 353.83 787.52 C 262.59 671.00 171.37 554.47 80.15 437.94 C 135.98 409.75 191.82 381.56 247.65 353.34 C 191.89 282.04 136.17 210.71 80.38 139.43 C 144.53 106.85 208.84 74.55 273.03 42.04 M 144.87 138.36 C 213.96 175.07 283.07 211.77 352.16 248.48 C 421.07 211.91 489.97 175.33 558.87 138.77 C 489.97 103.74 421.08 68.68 352.18 33.66 C 283.06 68.53 213.96 103.43 144.87 138.36 M 136.71 165.58 C 209.17 258.00 281.50 350.52 353.84 443.03 C 424.45 350.88 495.27 258.89 565.97 166.80 C 494.73 204.53 423.57 242.40 352.34 280.12 C 280.47 241.93 208.62 203.69 136.71 165.58 M 144.84 436.71 C 213.93 473.40 283.15 509.84 352.19 546.63 C 421.24 510.23 490.11 473.47 559.09 436.92 C 519.48 416.83 479.88 396.72 440.30 376.58 C 411.47 414.11 382.66 451.64 353.84 489.15 C 324.30 451.45 294.80 413.71 265.26 376.00 C 225.11 396.21 185.00 416.50 144.84 436.71 M 136.50 464.12 C 208.83 556.64 281.25 649.10 353.61 741.60 C 424.45 649.48 495.20 557.28 566.02 465.13 C 494.70 502.96 423.44 540.90 352.12 578.71 C 280.26 540.48 208.38 502.30 136.50 464.12 Z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -1,24 +0,0 @@
export const environment = {
production: true,
uploadImageHostname: "node.deso.org",
verificationEndpointHostname: "https://node.deso.org",
uploadVideoHostname: "node.deso.org",
identityURL: "https://identity.deso.org",
supportEmail: "node.admin@bitclout.com",
dd: {
apiKey: "DCEB26AC8BF47F1D7B4D87440EDCA6",
jsPath: "https://bitclout.com/tags.js",
ajaxListenerPath: "bitclout.com/api",
endpoint: "https://bitclout.com/js/",
},
amplitude: {
key: "23345b239094949bc7f3402cebe9e5d2",
domain: "amp.bitclout.com",
},
node: {
id: 2,
name: "BitClout",
url: "https://bitclout.com",
logoAssetDir: "/assets/bitclout/",
},
};

View file

@ -1,24 +1,4 @@
export const environment = {
production: true,
uploadImageHostname: "node.deso.org",
verificationEndpointHostname: "https://node.deso.org",
uploadVideoHostname: "node.deso.org",
identityURL: "https://identity.deso.org",
supportEmail: "",
dd: {
apiKey: "DCEB26AC8BF47F1D7B4D87440EDCA6",
jsPath: "https://bitclout.com/tags.js",
ajaxListenerPath: "bitclout.com/api",
endpoint: "https://bitclout.com/js/",
},
amplitude: {
key: "",
domain: "",
},
node: {
id: 1,
name: "DeSo",
url: "https://node.deso.org",
logoAssetDir: "/assets/deso/",
},
identityURL: "http://localhost:4201",
};

View file

@ -4,25 +4,6 @@
export const environment = {
production: false,
uploadImageHostname: "node.deso.org",
verificationEndpointHostname: "https://node.deso.org",
uploadVideoHostname: "node.deso.org",
identityURL: "https://identity.deso.org",
supportEmail: "",
dd: {
apiKey: "DCEB26AC8BF47F1D7B4D87440EDCA6",
jsPath: "https://bitclout.com/tags.js",
ajaxListenerPath: "bitclout.com/api",
endpoint: "https://bitclout.com/js/",
},
amplitude: {
key: "",
domain: "",
},
node: {
id: 1,
name: "DeSo",
url: "https://node.deso.org",
logoAssetDir: "/assets/deso/",
},
backendHostname: 'localhost:8090',
identityURL: "http://localhost:4201",
};

View file

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="referrer" content="no-referrer" />
<title>Welcome to BitClout</title>
<meta name="description" content="BitClout is a platform owned by its users. Bitcoin is decentralizing money, BitClout is decentralizing social media." />
<meta name="theme-color" content="#eeeeee" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#121212" media="(prefers-color-scheme: dark)">
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://bitclout.com/assets/bitclout/camelcase_logo_og.jpg" />
<meta property="og:title" content="Welcome to BitClout" />
<meta property="og:description" content="BitClout is a platform owned by its users. Bitcoin is decentralizing money, BitClout is decentralizing social media." />
<meta property="og:site_name" content="BitClout" />
<meta property='og:image' content="https://bitclout.com/assets/bitclout/camelcase_logo_og.jpg" />
<meta property="og:image:secure_url" content="https://bitclout.com/assets/bitclout/camelcase_logo_og.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:alt" content="bitclout.com Logo on White Background" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://bitclout.com" />
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=0.9" />
<link rel="icon" type="image/x-icon" href="/assets/bitclout/favicon.png" />
<link href="/vendor/bootstrap.min.css" rel="stylesheet" />
<link href="/vendor/roboto-mono.css" rel="stylesheet" />
<link href="/vendor/roboto.css" rel="stylesheet" />
<link href="/vendor/fontello.css" rel="stylesheet" />
<link href="/vendor/bs-datepicker.css" rel="stylesheet" />
<script src="https://kit.fontawesome.com/070ca4195b.js" crossorigin="anonymous"></script>
<!-- Add to iOS home screen -->
<link rel="manifest" href="/assets/app.webmanifest" />
<script async src="/vendor/pwacompat.min.js" crossorigin="anonymous"></script>
<link rel="icon" type="image/png" href="/assets/bitclout/logo-512.png" sizes="512x512" />
<link rel="icon" type="image/png" href="/assets/bitclout/logo-192.png" sizes="192x192" />
<link rel="apple-touch-icon" sizes="192x192" href="/assets/bitclout/logo-192.png" type="image/png" />
<link rel="apple-touch-icon" sizes="512x512" href="/assets/bitclout/logo-512.png" type="image/png" />
</head>
<body app-theme>
<app-root></app-root>
</body>
</html>

View file

@ -8,16 +8,6 @@
<meta name="theme-color" content="#eeeeee" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#121212" media="(prefers-color-scheme: dark)">
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://node.deso.org/assets/deso/camelcase_logo_og.jpg" />
<meta property="og:title" content="Welcome to DeSo" />
<meta property="og:description" content="DeSo is a platform owned by its users. Bitcoin is decentralizing money, DeSo is decentralizing social media." />
<meta property="og:site_name" content="DeSo" />
<meta property='og:image' content="https://node.deso.org/assets/deso/camelcase_logo_og.jpg" />
<meta property="og:image:secure_url" content="https://node.deso.org/assets/deso/camelcase_logo_og.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:alt" content="DeSo Logo on White Background" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://node.deso.org" />
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=0.9" />

View file

@ -1,32 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage/electron-angular-app'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

View file

@ -1,44 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/identity'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

73
package-lock.json generated
View file

@ -23,7 +23,7 @@
"base64-js": "^1.5.1",
"bip32": "^2.0.6",
"bip39": "^3.0.3",
"bitcoinjs-lib": "github:lbryio/bitcoinjs-lib#a1c5104f4a892a775b2d97b9fc88a1ba5b17403e",
"bitcoinjs-lib": "github:lbryio/bitcoinjs-lib#ae14ef1355d2d8bb3e9aca98a01201d279e085bf",
"crypto-browserify": "^3.12.0",
"ecpair": "^1.0.1",
"elliptic": "^6.5.4",
@ -32,8 +32,6 @@
"jsonwebtoken": "^8.5.1",
"key-encoder": "^2.0.3",
"ngx-bootstrap": "^6.2.0",
"ngx-cookie": "^5.0.2",
"ngx-intl-tel-input": "^3.1.1",
"readable-stream": "^3.6.0",
"reflect-metadata": "^0.1.13",
"rxjs": "~6.6.0",
@ -3400,8 +3398,8 @@
},
"node_modules/bitcoinjs-lib": {
"version": "6.0.1",
"resolved": "git+ssh://git@github.com/lbryio/bitcoinjs-lib.git#a1c5104f4a892a775b2d97b9fc88a1ba5b17403e",
"integrity": "sha512-xluOW5MDgy2GTFAW8Vg7qYAgAlOTTaFWlogg8ejNetNRSZ8tg3go9XkEHdQXFhf1mDxKl55ybvSl0ie1ApiTrA==",
"resolved": "git+ssh://git@github.com/lbryio/bitcoinjs-lib.git#ae14ef1355d2d8bb3e9aca98a01201d279e085bf",
"integrity": "sha512-AwoS80uq7ADqozrLW3aDjSvLZpQQn7a2Jt6Qt9DOiYTcwjxWarWEt+ntkNskzk63Azs779Wb0suD6KqWbJ/U5A==",
"license": "MIT",
"dependencies": {
"bech32": "^2.0.0",
@ -7200,15 +7198,6 @@
"node": ">=10"
}
},
"node_modules/google-libphonenumber": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.25.tgz",
"integrity": "sha512-M/b5mij5o2aGnbe+Id9O3847jBtP0baW61foFkevxBxbuV4LH9AcujjYLd2UVkUPKVdMpKyBZxfeNwdxqobQFQ==",
"peer": true,
"engines": {
"node": ">=0.10"
}
},
"node_modules/graceful-fs": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@ -10228,34 +10217,6 @@
"@angular/core": ">=7.0.0"
}
},
"node_modules/ngx-cookie": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/ngx-cookie/-/ngx-cookie-5.0.2.tgz",
"integrity": "sha512-auivWhAhC5bW1HssvtQild1TREHWb1JtcKO0e+VGe9T7LHrfi5w2qcP8C58ly64PT+brZHQBvT1Azb7a6goHZA==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": ">9.0.0",
"@angular/core": ">9.0.0"
}
},
"node_modules/ngx-intl-tel-input": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/ngx-intl-tel-input/-/ngx-intl-tel-input-3.1.1.tgz",
"integrity": "sha512-jk/6dBBDo9Z9QNBzFvJKQs159mZtKfC+/hF3MDoMpya8xzOz7Tbwh5qNsHaIFtzDx82Fv1Ji9+0VPWIv1P0fIQ==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "8.x - 11.x",
"@angular/core": "8.x - 11.x",
"@angular/forms": "8.x - 11.x",
"google-libphonenumber": "^3.2.3",
"intl-tel-input": "^17.0.3",
"ngx-bootstrap": "^6.0.0"
}
},
"node_modules/nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -21472,9 +21433,9 @@
}
},
"bitcoinjs-lib": {
"version": "git+ssh://git@github.com/lbryio/bitcoinjs-lib.git#a1c5104f4a892a775b2d97b9fc88a1ba5b17403e",
"integrity": "sha512-xluOW5MDgy2GTFAW8Vg7qYAgAlOTTaFWlogg8ejNetNRSZ8tg3go9XkEHdQXFhf1mDxKl55ybvSl0ie1ApiTrA==",
"from": "bitcoinjs-lib@lbryio/bitcoinjs-lib#a1c5104f4a892a775b2d97b9fc88a1ba5b17403e",
"version": "git+ssh://git@github.com/lbryio/bitcoinjs-lib.git#ae14ef1355d2d8bb3e9aca98a01201d279e085bf",
"integrity": "sha512-AwoS80uq7ADqozrLW3aDjSvLZpQQn7a2Jt6Qt9DOiYTcwjxWarWEt+ntkNskzk63Azs779Wb0suD6KqWbJ/U5A==",
"from": "bitcoinjs-lib@github:lbryio/bitcoinjs-lib#ae14ef1355d2d8bb3e9aca98a01201d279e085bf",
"requires": {
"bech32": "^2.0.0",
"bip174": "^2.0.1",
@ -24755,12 +24716,6 @@
"slash": "^3.0.0"
}
},
"google-libphonenumber": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.25.tgz",
"integrity": "sha512-M/b5mij5o2aGnbe+Id9O3847jBtP0baW61foFkevxBxbuV4LH9AcujjYLd2UVkUPKVdMpKyBZxfeNwdxqobQFQ==",
"peer": true
},
"graceful-fs": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@ -27259,22 +27214,6 @@
"integrity": "sha512-5WKHo6/ltkenw4UyXZwED8rODCgp2RGbWurzYzZsF/gH1JO5SN7TJ+AL6kXYk6XM42sDA2WhN9Db+ZPNjiyHnA==",
"requires": {}
},
"ngx-cookie": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/ngx-cookie/-/ngx-cookie-5.0.2.tgz",
"integrity": "sha512-auivWhAhC5bW1HssvtQild1TREHWb1JtcKO0e+VGe9T7LHrfi5w2qcP8C58ly64PT+brZHQBvT1Azb7a6goHZA==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-intl-tel-input": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/ngx-intl-tel-input/-/ngx-intl-tel-input-3.1.1.tgz",
"integrity": "sha512-jk/6dBBDo9Z9QNBzFvJKQs159mZtKfC+/hF3MDoMpya8xzOz7Tbwh5qNsHaIFtzDx82Fv1Ji9+0VPWIv1P0fIQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View file

@ -28,7 +28,7 @@
"base64-js": "^1.5.1",
"bip32": "^2.0.6",
"bip39": "^3.0.3",
"bitcoinjs-lib": "github:lbryio/bitcoinjs-lib#a1c5104f4a892a775b2d97b9fc88a1ba5b17403e",
"bitcoinjs-lib": "github:lbryio/bitcoinjs-lib#ae14ef1355d2d8bb3e9aca98a01201d279e085bf",
"crypto-browserify": "^3.12.0",
"ecpair": "^1.0.1",
"elliptic": "^6.5.4",
@ -37,8 +37,6 @@
"jsonwebtoken": "^8.5.1",
"key-encoder": "^2.0.3",
"ngx-bootstrap": "^6.2.0",
"ngx-cookie": "^5.0.2",
"ngx-intl-tel-input": "^3.1.1",
"readable-stream": "^3.6.0",
"reflect-metadata": "^0.1.13",
"rxjs": "~6.6.0",

View file

@ -1,60 +1,228 @@
import {Injectable} from '@angular/core';
import {CryptoService} from './crypto.service';
import {GlobalVarsService} from './global-vars.service';
import {AccessLevel, Network, PrivateUserInfo, PublicUserInfo} from '../types/identity';
import HDKey from 'hdkey';
import {HubService} from './hub.service';
import {SigningService} from './signing.service';
import {
AccessLevel,
ActionType,
PrivateAccountInfo,
PrivateChannelInfo,
PublicChannelInfo,
} from '../types/identity';
@Injectable({
providedIn: 'root'
})
export class AccountService {
private static usersStorageKey = 'users';
private static levelsStorageKey = 'levels';
private static publicKeyRegex = /^[a-zA-Z0-9]{54,55}$/;
private static walletStorageKey = 'wallet';
private static channelsStorageKey = 'channels';
private static accessStorageKey = 'access';
constructor(
private cryptoService: CryptoService,
private globalVars: GlobalVarsService,
private signingService: SigningService,
) { }
// Public Getters
/*
getPublicKeys(): any {
return Object.keys(this.getPrivateUsers());
What we're keeping in local storage. TODO - make these into data structs.
// The wallet, taken from wallet Sync. Perhaps even with local changes to be
// 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,
wallet: "...",
}
getEncryptedUsers(): {[key: string]: PublicUserInfo} {
const hostname = this.globalVars.hostname;
const privateUsers = this.getPrivateUsers();
const publicUsers: {[key: string]: PublicUserInfo} = {};
// Access information. For each hostname, what channel is logged in, and what
// level of permission for various actions did the user give?
localStorage["access"] = {
// There can be multiple hostnames
"<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>",
}
}
}
}
for (const publicKey of Object.keys(privateUsers)) {
const privateUser = privateUsers[publicKey];
const accessLevel = this.getAccessLevel(publicKey, hostname);
if (accessLevel === AccessLevel.None) {
localStorage["channels"] = {
// There can be multiple channels
"<channels>": PrivateChannelInfo,
}
*/
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));
}
// TODO - PrivateAccountInfo should just contain a bip32 node and an
// "address" (account ID) that it generates when reading from storage. It
// wouldn't write these values back to storage, they're a function of the
// data already there. It would clean up the code by removing the need for
// bip32FromAccount, getAddress, and getAddressFromBip32.
public getAccounts(): PrivateAccountInfo[] {
const wallet = this.getWallet()
if (wallet === null) {
return []
}
const filteredAccounts: PrivateAccountInfo[] = [];
for (const account of wallet.accounts) {
// Only include accounts from the current network
if (account.ledger !== this.globalVars.network) {
continue;
}
const encryptedSeedHex = this.cryptoService.encryptSeedHex(privateUser.seedHex, hostname);
const accessLevelHmac = this.cryptoService.accessLevelHmac(accessLevel, privateUser.seedHex);
publicUsers[publicKey] = {
hasExtraText: privateUser.extraText?.length > 0,
encryptedSeedHex,
network: privateUser.network,
accessLevel,
accessLevelHmac,
};
filteredAccounts.push(account);
}
return filteredAccounts
}
return publicUsers;
private clearChannels() {
localStorage.setItem(AccountService.channelsStorageKey, JSON.stringify(null));
}
getAccessLevel(publicKey: string, hostname: string): AccessLevel {
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
const hostMapping = levels[hostname] || {};
const accessLevel = hostMapping[publicKey];
// TODO - This function is async due to the http call, so now I have to
// rethink the guarantees about login state being based on the data being in
// localStorage.
//
// TODO error handling?
public updateChannels(): Observable<null> {
// Where we accumulate the channels for all accounts through all of the
// recursions
let channels: {[key: string]: {[key: string]: PrivateChannelInfo}} = {};
// Return this so the caller can do something pending this completing
// (perhaps keep the login state orderly)
// TODO - there's got to be a better way.
return new Observable(subscriber => {
accumulateChannelsForAccounts(accounts: PrivateAccountInfo[]) {
if(!accounts.length){
// We got the channels for all accounts. Give it to the subscriber so we can add it to local storage.
subscriber.next(channels)
subscriber.complete()
return
}
this.signingService.getChannelsForAccount(accounts[0])
.pipe(
map(acountChannels => {
const accountId = this.signingService.getAddress(account)
// `acountChannels` is an array. We want the same data in an object,
// keyed by the pubKey field.
channelsByPubkey = Object.fromEntries(
acountChannels.map(channel => [channel.pubKeyId, channel])
)
channels[accountId] = channelsByPubkey
// Call again, omitting the account we just handled.
accumulateChannelsForAccount(accounts.slice(1))
})
)
}
// Kick it off with all accounts.
accumulateChannelsForAccounts(this.getAccounts())
}).pipe(
map(channels => {
localStorage.setItem(AccountService.channelsStorageKey, JSON.stringify(channels));
return null
})
).subscribe() // We want to actually kick off these actions if this function is called (see pipe vs subscribe)
}
public hasChannels() {
return !!localStorage.getItem(AccountService.channelsStorageKey);
}
public getChannelsPrivate(): {[key: string]: PrivateChannelInfo} {
return JSON.parse(localStorage.getItem(AccountService.channelsStorageKey) || '{}');
}
// returns {accountId: [PublicChannelInfo]}
public getChannelsPublic(): {[key: string]: PublicChannelInfo} {
const privateChannels: {[key: string]: PrivateChannelInfo} = this.getChannelsPrivate()
const publicChannels: {[key: string]: PublicChannelInfo} = {}
for(const accountId of Object.keys(privateChannels)) {
publicChannels[accountId] = {
claimId: privateChannels[accountId].claimId,
name: privateChannels[accountId].name,
normalizedName: privateChannels[accountId].normalizedName,
pubKeyId: privateChannels[accountId].pubKeyId,
}
}
return publicChannels
}
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.getChannelsPublic()
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)) {
return accessLevel;
@ -63,80 +231,63 @@ export class AccountService {
}
}
// Public Modifiers
addUser(keychain: HDKey, mnemonic: string, extraText: string, network: Network): string {
const seedHex = this.cryptoService.keychainToSeedHex(keychain);
return this.addPrivateUser({
seedHex,
mnemonic,
extraText,
network,
});
public setAccessLevel(hostname: string, channelClaimId: string, action: ActionType, level: AccessLevel) {
const access = JSON.parse(localStorage.getItem(AccountService.accessStorageKey) || '{}');
if (!(hostname in access)) {
access[hostname] = {levels: {}}
}
if (!(channelClaimId in access[hostname].levels)) {
access[hostname].levels[channelClaimId] = {}
}
access[hostname].levels[channelClaimId][action] = level
localStorage.setItem(AccountService.accessStorageKey, JSON.stringify(access));
}
deleteUser(publicKey: string): void {
const privateUsers = this.getPrivateUsersRaw();
delete privateUsers[publicKey];
this.setPrivateUsersRaw(privateUsers);
public setAccessCurrentChannel(hostname: string, channelClaimId: string) {
const access = JSON.parse(localStorage.getItem(AccountService.accessStorageKey) || '{}');
if (!(hostname in access)) {
access[hostname] = {levels: {}}
}
access[hostname].currentChannel = channelClaimId
localStorage.setItem(AccountService.accessStorageKey, JSON.stringify(access));
}
setAccessLevel(publicKey: string, hostname: string, accessLevel: AccessLevel): void {
const levels = JSON.parse(localStorage.getItem(AccountService.levelsStorageKey) || '{}');
levels[hostname] ||= {};
levels[hostname][publicKey] = accessLevel;
localStorage.setItem(AccountService.levelsStorageKey, JSON.stringify(levels));
public walletLogout() {
this.putWallet(null)
this.clearAccess()
this.clearChannels()
}
// Private Getters and Modifiers
public walletLogin(wallet: object | null) {
// no ambiguity from half-completed actions
// make sure we're fully logged out
this.walletLogout()
// TEMP: public for import flow
public addPrivateUser(userInfo: PrivateUserInfo): string {
const privateUsers = this.getPrivateUsersRaw();
const privateKey = this.cryptoService.seedHexToPrivateKey(userInfo.seedHex);
const publicKey = this.cryptoService.privateKeyToDeSoPublicKey(privateKey, userInfo.network);
privateUsers[publicKey] = userInfo;
this.setPrivateUsersRaw(privateUsers);
return publicKey;
this.putWallet(wallet)
this.initAccess()
this.updateChannels()
}
private getPrivateUsers(): {[key: string]: PrivateUserInfo} {
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;
// 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()
)
}
// Get rid of some users who have invalid public keys
if (!publicKey.match(AccountService.publicKeyRegex)) {
this.deleteUser(publicKey);
continue;
// 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));
}
filteredPrivateUsers[publicKey] = privateUser;
public appLogout(hostname: string): void {
this.appClearAccess(hostname)
}
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));
}
}

View file

@ -1,38 +1,35 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {TestSignComponent} from './test-sign/test-sign.component';
import {TestLbryLogInComponent} from './test-lbry-log-in/test-lbry-log-in.component';
import {LogInWalletComponent} from './log-in-wallet/log-in-wallet.component';
import {TestSignTransactionComponent} from './test-sign-transaction/test-sign-transaction.component';
import {EmbedComponent} from './embed/embed.component';
import {HomeComponent} from './home/home.component';
import {LogoutComponent} from './logout/logout.component';
import {SignUpComponent} from './sign-up/sign-up.component';
import {LogInComponent} from './log-in/log-in.component';
import {LogInAppComponent} from './log-in-app/log-in-app.component';
import {ApproveComponent} from './approve/approve.component';
import {LoadSeedComponent} from './load-seed/load-seed.component';
export class RouteNames {
public static TEST_SIGN = 'test-sign';
public static TEST_LBRY_LOG_IN = 'test-lbry-log-in';
public static LOG_IN_WALLET = 'log-in-wallet';
public static TEST_SIGN_TRANSACTION = 'test-sign-transaction';
public static EMBED = 'embed';
public static LOGOUT = 'logout';
public static SIGN_UP = 'sign-up';
public static LOG_IN = 'log-in';
public static LOAD_SEED = 'load-seed';
public static LOG_IN_APP = 'log-in-app';
public static APPROVE = 'approve';
}
const routes: Routes = [
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: RouteNames.TEST_SIGN, component: TestSignComponent, pathMatch: 'full' },
{ path: RouteNames.TEST_LBRY_LOG_IN, component: TestLbryLogInComponent, pathMatch: 'full' },
{ path: RouteNames.LOG_IN_WALLET, component: LogInWalletComponent, pathMatch: 'full' },
{ path: RouteNames.TEST_SIGN_TRANSACTION, component: TestSignTransactionComponent, pathMatch: 'full' },
{ path: RouteNames.EMBED, component: EmbedComponent, pathMatch: 'full' },
{ path: RouteNames.LOGOUT, component: LogoutComponent, pathMatch: 'full' },
{ path: RouteNames.SIGN_UP, component: SignUpComponent, pathMatch: 'full' },
{ path: RouteNames.LOG_IN, component: LogInComponent, pathMatch: 'full' },
{ path: RouteNames.LOAD_SEED, component: LoadSeedComponent, pathMatch: 'full' },
{ path: RouteNames.LOG_IN_APP, component: LogInAppComponent, pathMatch: 'full' },
{ path: RouteNames.APPROVE, component: ApproveComponent, pathMatch: 'full' },
];

View file

@ -22,17 +22,12 @@ export class AppComponent implements OnInit {
// load params
const params = new URLSearchParams(window.location.search);
const accessLevelRequest = params.get('accessLevelRequest');
if (accessLevelRequest) {
this.globalVars.accessLevelRequest = parseInt(accessLevelRequest, 10);
}
if (params.get('webview')) {
this.globalVars.webview = true;
}
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.
@ -49,6 +44,17 @@ export class AppComponent implements OnInit {
if (this.globalVars.callback) {
// If callback is set, we won't be sending the initialize message.
// TODO - Why is it being set to 'localhost'? Seems arbitrary. Seems
// like we need this set to the correct value?
//
// It could be a ui security problem. we say "`this.globalVars.hostname`
// wants to do `transaction`". If it's set to "localhost" they might get
// the wrong idea. Or maybe I have no idea what this actually means.
// Or maybe localhost is actually safe since it's unlikely enough that
// somebody would be trying to pwn themselves from localhost.
throw "figure this out or delete this code branch"
this.globalVars.hostname = 'localhost';
this.finishInit();
} else if (this.globalVars.webview || this.globalVars.inTab || this.globalVars.inFrame()) {

View file

@ -9,22 +9,19 @@ import { EmbedComponent } from './embed/embed.component';
import { HomeComponent } from './home/home.component';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {IdentityService} from './identity.service';
import {CookieModule} from 'ngx-cookie';
import { LogoutComponent } from './logout/logout.component';
import { BannerComponent } from './banner/banner.component';
import { SignUpComponent } from './sign-up/sign-up.component';
import {AccountService} from './account.service';
import {EntropyService} from './entropy.service';
import { LogInComponent } from './log-in/log-in.component';
import { LogInAppComponent } from './log-in-app/log-in-app.component';
import {HttpClientModule} from '@angular/common/http';
import { ApproveComponent } from './approve/approve.component';
import { LoadSeedComponent } from './load-seed/load-seed.component';
import { ErrorCallbackComponent } from './error-callback/error-callback.component';
import { NgxIntlTelInputModule } from 'ngx-intl-tel-input';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TestSignComponent } from './test-sign/test-sign.component';
import { TestSignTransactionComponent } from './test-sign-transaction/test-sign-transaction.component';
import { TestLbryLogInComponent } from './test-lbry-log-in/test-lbry-log-in.component'
import { LogInWalletComponent } from './log-in-wallet/log-in-wallet.component'
@NgModule({
declarations: [
@ -34,13 +31,12 @@ import { TestLbryLogInComponent } from './test-lbry-log-in/test-lbry-log-in.comp
LogoutComponent,
BannerComponent,
SignUpComponent,
LogInComponent,
LogInAppComponent,
ApproveComponent,
LoadSeedComponent,
ErrorCallbackComponent,
TestSignComponent,
TestSignTransactionComponent,
TestLbryLogInComponent,
LogInWalletComponent,
],
imports: [
BrowserModule,
@ -49,10 +45,8 @@ import { TestLbryLogInComponent } from './test-lbry-log-in/test-lbry-log-in.comp
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
NgxIntlTelInputModule,
MatFormFieldModule,
MatTooltipModule,
CookieModule.forRoot()
],
providers: [
IdentityService,

View file

@ -9,9 +9,9 @@
{{ globalVars.hostname }} wants to {{ transactionDescription }}
</p>
<br/>
<div *ngIf="transactionDeSoSpent">
<div *ngIf="transactionSpent">
<p class="pb-15px">
Total Cost: {{ transactionDeSoSpent }} $DESO
Total Cost: {{ transactionSpent }} $DESO
</p>
</div>
<div class="d-flex align-items-end justify-content-between">

View file

@ -1,10 +1,9 @@
import { Component, OnInit } from '@angular/core';
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {CryptoService} from '../crypto.service';
import {IdentityService} from '../identity.service';
import {AccountService} from '../account.service';
import {GlobalVarsService} from '../global-vars.service';
import {SigningService} from '../signing.service';
// import {SigningService} from '../signing.service';
import {BackendAPIService, User} from '../backend-api.service';
import {Observable, of} from 'rxjs';
import {map} from 'rxjs/operators';
@ -33,7 +32,6 @@ import {
TransactionMetadataDAOCoin,
TransactionMetadataTransferDAOCoin
} from '../../lib/deso/transaction';
import bs58check from 'bs58check';
@Component({
selector: 'app-approve',
@ -46,29 +44,32 @@ export class ApproveComponent implements OnInit {
transactionHex: any;
username: any;
transactionDescription: any;
transactionDeSoSpent: string | boolean = false;
transactionSpent: string | boolean = false;
constructor(
private activatedRoute: ActivatedRoute,
private cryptoService: CryptoService,
private identityService: IdentityService,
private accountService: AccountService,
public globalVars: GlobalVarsService,
private signingService: SigningService,
// private signingService: SigningService,
private backendApi: BackendAPIService,
) { }
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(params => {
this.transactionHex = params.tx;
this.backendApi.GetTransactionSpending(this.transactionHex).subscribe( res => {
this.transactionDeSoSpent = res ? this.nanosToUnitString(res) : false;
});
// TODO - for LBRY
this.transactionSpent = false;
const txBytes = new Buffer(this.transactionHex, 'hex');
this.transaction = Transaction.fromBytes(txBytes)[0];
this.publicKey = this.base58KeyCheck(this.transaction.publicKey);
this.generateTransactionDescription();
/*
TODO this.accountService.getActiveChannelAccessLevel
*/
});
}
@ -77,22 +78,26 @@ export class ApproveComponent implements OnInit {
}
onSubmit(): void {
const signedTransactionHex = this.signingService.signTransaction(this.seedHex(), this.transactionHex);
// TODO
throw "replace all of this transaction parsing and checking with bitcoinjs-lib."
/*
const seedHex = ""
const signedTransactionHex = this.signingService.signTransaction(seedHex, this.transactionHex);
this.finishFlow(signedTransactionHex);
*/
/*
TODO this.accountService.setAccessLevel if people want to keep allowing the action
*/
}
finishFlow(signedTransactionHex?: string): void {
this.identityService.login({
users: this.accountService.getEncryptedUsers(),
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
signedTransactionHex,
});
}
seedHex(): string {
const encryptedSeedHex = this.accountService.getEncryptedUsers()[this.publicKey].encryptedSeedHex;
return this.cryptoService.decryptSeedHex(encryptedSeedHex, this.globalVars.hostname);
}
generateTransactionDescription(): void {
let description = 'sign an unknown transaction';
let publicKeys: string[] = [];
@ -252,8 +257,13 @@ export class ApproveComponent implements OnInit {
}
base58KeyCheck(keyBytes: Uint8Array): string {
const prefix = CryptoService.PUBLIC_KEY_PREFIXES[this.globalVars.network].deso;
return bs58check.encode(Buffer.from([...prefix, ...keyBytes]));
// TODO
throw "replace all of this transaction parsing and checking with bitcoinjs-lib."
return ""
// TODO Don't use this.globalVars.network here, use the network specified
// in the relevant account.ledger (assuming we even really need network)
// const prefix = CryptoService.PUBLIC_KEY_PREFIXES[this.globalVars.network as Network].deso;
// return bs58check.encode(Buffer.from([...prefix, ...keyBytes]));
}
hexNanosToUnitString(nanos: Buffer): string {
@ -273,7 +283,7 @@ export class ApproveComponent implements OnInit {
return of(description);
}
// Otherwise, we hit get-users-stateless to fetch profiles.
return this.backendApi.GetUsersStateless(publicKeys, true).pipe((map(res => {
return this.backendApi.GetUsersStateless(publicKeys).pipe((map(res => {
const userList = res.UserList;
// If the response has no users, return the description as is.
if (userList.length === 0) {

View file

@ -1,19 +1,12 @@
import { Injectable } from '@angular/core';
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import {catchError, map} from 'rxjs/operators';
import {environment} from '../environments/environment';
import {SigningService} from './signing.service';
import {AccountService} from './account.service';
import {CryptoService} from './crypto.service';
import {GlobalVarsService} from './global-vars.service';
import {UserProfile} from '../types/identity';
export class ProfileEntryResponse {
Username: string | null = null;
Description: string | null = null;
ProfilePic?: string;
PublicKeyBase58Check?: string;
}
export class User {
@ -25,98 +18,83 @@ export class User {
providedIn: 'root'
})
export class BackendAPIService {
walletSyncEndpoint = `https://${environment.walletSyncHostname}/api/v0`;
endpoint = `https://${environment.nodeHostname}/api/v0`;
constructor(
private httpClient: HttpClient,
private cryptoService: CryptoService,
private signingService: SigningService,
private accountService: AccountService,
private globalVars: GlobalVarsService,
) { }
post(path: string, body: any): Observable<any> {
return this.httpClient.post<any>(`${this.endpoint}/${path}`, body);
}
jwtPost(path: string, publicKey: string, body: any): Observable<any> {
const publicUserInfo = this.accountService.getEncryptedUsers()[publicKey];
if (!publicUserInfo) {
return of(null);
}
const seedHex = this.cryptoService.decryptSeedHex(publicUserInfo.encryptedSeedHex, this.globalVars.hostname);
const jwt = this.signingService.signJWT(seedHex);
return this.post(path, {...body, ...{JWT: jwt}});
}
// Error parsing
stringifyError(err: any): string {
return err?.error?.error || JSON.stringify(err);
}
// When SkipForLeaderboard is true, this endpoint only returns ProfileEntryResponse, IsGraylisted, IsBlacklisted,
// IsAdmin, and IsSuperAdmin for each user.
// When SkipForLeaderboard is false, we also fetch the user's balance, profiles this user follows, hodlings, and
// UserMetadata. Oftentimes, this information is not needed and excluding it significantly improves performance.
GetUsersStateless(
publicKeys: string[], SkipForLeaderboard: boolean = false,
publicKeys: string[]
): Observable<{ UserList: User[]}> {
return this.httpClient.post<any>(
`${this.endpoint}/get-users-stateless`,
{
PublicKeysBase58Check: publicKeys,
SkipForLeaderboard,
},
);
}
GetUserProfiles(
GetUsernames(
publicKeys: string[]
): Observable<{[key: string]: UserProfile}> {
const userProfiles: {[key: string]: any} = {};
const req = this.GetUsersStateless(publicKeys, true);
): Observable<{[key: string]: string}> {
const usernames: {[key: string]: any} = {};
const req = this.GetUsersStateless(publicKeys);
if (publicKeys.length > 0) {
return req.pipe(
map( res => {
for (const user of res.UserList) {
userProfiles[user.PublicKeyBase58Check] = {
username: user.ProfileEntryResponse?.Username,
};
usernames[user.PublicKeyBase58Check] = user.ProfileEntryResponse?.Username
}
return userProfiles;
return usernames;
})
).pipe(
catchError(() => {
for(const publicKey of publicKeys) {
userProfiles[publicKey] = {};
usernames[publicKey] = "";
}
return of(userProfiles);
return of(usernames);
})
);
} else {
return of(userProfiles);
return of(usernames);
}
}
GetTransactionSpending(
transactionHex: string
): Observable<number> {
const req = this.httpClient.post<any>(
`${this.endpoint}/get-transaction-spending`,
// TODO - WIP, not using for now.
// This isn't what the current wallet sync API looks like, but it will
// likely change anyway. So this is an approximation with a stub for the time being.
WalletSyncLogin(
username: string,
password: string,
): Observable<{bodyJson: string, signature: string} | null> {
// A stub for now
const wallet : object | null = this.accountService.getWallet();
if (wallet === null) {
return of(null)
}
return of({
bodyJson: JSON.stringify(wallet, null, 2),
signature: "",
})
// Later we'll do something like this...
return this.httpClient.post<any>(
`${this.walletSyncEndpoint}/log-in`,
{
TransactionHex: transactionHex,
username: username,
password: password,
},
);
return req.pipe(
map( res => {
return res.TotalSpendingNanos as number;
})
).pipe(
catchError(() => {
return of(0);
})
);
}
}

View file

@ -1,113 +1,32 @@
import { Injectable } from '@angular/core';
import HDNode from 'hdkey';
import * as bip39 from 'bip39';
import HDKey from 'hdkey';
import {ec as EC} from 'elliptic';
import bs58check from 'bs58check';
import {CookieService} from 'ngx-cookie';
import {createHmac, createCipher, createDecipher, randomBytes} from 'crypto';
import {AccessLevel, Network} from '../types/identity';
import { GlobalVarsService } from './global-vars.service';
import {AccessLevel} from '../types/identity';
@Injectable({
providedIn: 'root'
})
export class CryptoService {
constructor(
private cookieService: CookieService,
private globalVars: GlobalVarsService
) {}
// TODO - LBRY?
static PUBLIC_KEY_PREFIXES = {
mainnet: {
deso: [0xcd, 0x14, 0x0],
},
testnet: {
deso: [0x11, 0xc2, 0x0],
}
};
// 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;
}
constructor() {}
// 32 bytes = 256 bits is plenty of entropy for encryption
newEncryptionKey(): string {
return randomBytes(32).toString('hex');
}
// TODO we won't need this soon right?
seedHexEncryptionStorageKey(hostname: string): string {
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 {
const storageKey = this.seedHexEncryptionStorageKey(hostname);
if (this.mustUseStorageAccess()) {
return !!this.cookieService.get(storageKey);
} else {
return !!localStorage.getItem(storageKey);
}
}
getWallet(hostname: string): object | 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
// previous key is overwritten, which is useful in logging out users.
@ -115,21 +34,11 @@ export class CryptoService {
const storageKey = this.seedHexEncryptionStorageKey(hostname);
let encryptionKey;
if (this.mustUseStorageAccess()) {
encryptionKey = this.cookieService.get(storageKey);
if (!encryptionKey || reset) {
encryptionKey = this.newEncryptionKey();
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
// everything to avoid returning unencrypted information.
@ -165,40 +74,6 @@ export class CryptoService {
return hmac === this.accessLevelHmac(accessLevel, seedHex);
}
encryptedSeedHexToPrivateKey(encryptedSeedHex: string, domain: string): EC.KeyPair {
const seedHex = this.decryptSeedHex(encryptedSeedHex, domain);
return this.seedHexToPrivateKey(seedHex);
}
mnemonicToKeychain(mnemonic: string, extraText?: string, nonStandard?: boolean): HDNode {
const seed = bip39.mnemonicToSeedSync(mnemonic, extraText);
// @ts-ignore
return HDKey.fromMasterSeed(seed).derive('m/44\'/0\'/0\'/0/0', nonStandard);
}
keychainToSeedHex(keychain: HDNode): string {
return keychain.privateKey.toString('hex');
}
seedHexToPrivateKey(seedHex: string): EC.KeyPair {
const ec = new EC('secp256k1');
return ec.keyFromPrivate(seedHex);
}
privateKeyToDeSoPublicKey(privateKey: EC.KeyPair, network: Network): string {
const prefix = CryptoService.PUBLIC_KEY_PREFIXES[network].deso;
const key = privateKey.getPublic().encode('array', true);
const prefixAndKey = Uint8Array.from([...prefix, ...key]);
return bs58check.encode(prefixAndKey);
}
publicKeyToDeSoPublicKey(publicKey: EC.KeyPair, network: Network): string {
const prefix = CryptoService.PUBLIC_KEY_PREFIXES[network].deso;
const key = publicKey.getPublic().encode('array', true);
return bs58check.encode(Buffer.from([...prefix, ...key]));
}
// Decode public key base58check to Buffer of secp256k1 public key
publicKeyToECBuffer(publicKey: string): Buffer {
// Sanity check similar to Base58CheckDecodePrefix from core/lib/base58.go
@ -213,4 +88,16 @@ export class CryptoService {
return new Buffer(publicKeyEC.getPublic('array'));
}
// TODO check that the signature for the walletStr is valid
checkSig(walletStr: string, walletSignature: string): boolean {
throw "implement me"
return true
}
// TODO find errors in the wallet. missing fields, etc. json-schema
validateWallet(wallet: object): string | null {
throw "implement me"
return null
}
}

View file

@ -1,14 +1,13 @@
import { Injectable } from '@angular/core';
import {AccessLevel, Network} from '../types/identity';
import {Network} from '../types/identity';
import {environment} from '../environments/environment';
@Injectable({
providedIn: 'root'
})
export class GlobalVarsService {
network = Network.mainnet;
network : Network = Network.MainNet;
hostname = '';
accessLevelRequest = AccessLevel.ApproveAll;
inTab = !!window.opener;
webview = false;
@ -17,7 +16,6 @@ export class GlobalVarsService {
callback = '';
callbackInvalid = false;
constructor() { }
inFrame(): boolean {

View 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();
});
});

44
src/app/hub.service.ts Normal file
View file

@ -0,0 +1,44 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {environment} from '../environments/environment';
@Injectable({
providedIn: 'root'
})
export class HubService {
hubEndpoint = '';
constructor(
private httpClient: HttpClient,
) {
const hubHostname = environment.hubHostnames[
Math.floor(Math.random() * environment.hubHostnames.length)
];
this.hubEndpoint = `https://${hubHostname}`;
}
post(path: string, body: any): Observable<any> {
return this.httpClient.post<any>(`${this.hubEndpoint}/${path}`, body);
}
// 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(),
}]
}
}

View file

@ -1,36 +1,11 @@
import {Injectable} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {v4 as uuid} from 'uuid';
import {AccessLevel, PublicUserInfo} from '../types/identity';
import {AccessLevel, PublicChannelInfo} from '../types/identity';
import {CryptoService} from './crypto.service';
import {GlobalVarsService} from './global-vars.service';
import {CookieService} from 'ngx-cookie';
import {SigningService} from './signing.service';
import {HttpParams} from '@angular/common/http';
import {
Transaction,
TransactionMetadataBasicTransfer,
TransactionMetadataBitcoinExchange,
TransactionMetadataCreatorCoin,
TransactionMetadataCreatorCoinTransfer,
TransactionMetadataFollow,
TransactionMetadataLike,
TransactionMetadataPrivateMessage,
TransactionMetadataSubmitPost,
TransactionMetadataSwapIdentity,
TransactionMetadataUpdateBitcoinUSDExchangeRate,
TransactionMetadataUpdateGlobalParams,
TransactionMetadataUpdateProfile,
TransactionMetadataNFTTransfer,
TransactionMetadataAcceptNFTTransfer,
TransactionMetadataBurnNFT,
TransactionMetadataNFTBid,
TransactionMetadataAcceptNFTBid,
TransactionMetadataUpdateNFT,
TransactionMetadataCreateNFT,
TransactionMetadataDAOCoin,
TransactionMetadataTransferDAOCoin
} from '../lib/deso/transaction';
@Injectable({
providedIn: 'root'
@ -48,12 +23,30 @@ export class IdentityService {
constructor(
private cryptoService: CryptoService,
private globalVars: GlobalVarsService,
private cookieService: CookieService,
private signingService: SigningService,
) {
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
initialize(): Observable<any> {
@ -65,8 +58,7 @@ export class IdentityService {
}
login(payload: {
users: {[key: string]: PublicUserInfo},
publicKeyAdded?: string,
channel: PublicChannelInfo | null,
signedUp?: boolean
signedTransactionHex?: string,
addresses?: string[],
@ -89,11 +81,14 @@ export class IdentityService {
// Incoming Messages
private handleSign(data: any): void {
const { id, payload: { encryptedSeedHex, transactionHex } } = data;
// TODO
throw "implement for lbry"
/*
const transaction or action details = data;
// This will tell us whether we need full signing access or just ApproveLarge
// level of access.
const requiredAccessLevel = this.getRequiredAccessLevel(transactionHex);
const requiredAccessLevel = this.getRequiredAccessLevel(transaction or action details);
// In the case that approve() fails, it responds with a message indicating
// that approvalRequired = true, which the caller can then uses to trigger
@ -102,24 +97,27 @@ export class IdentityService {
return;
}
// If we get to this point, no approval UI was required. This typically
// happens if the caller has full signing access or signing access for
// non-spending txns such as like, post, update profile, etc. In the
// latter case we need a subsequent check to ensure that the txn is not
// sending money to any public keys other than the sender himself.
if (!this.approveSpending(data)) {
return;
}
const seedHex = this.cryptoService.decryptSeedHex(encryptedSeedHex, this.globalVars.hostname);
const signedTransactionHex = this.signingService.signTransaction(seedHex, transactionHex);
// TODO - this.signingService.signPSBT instead
if it's a transaction
const signedTransactionHex = this.signingService.signPSBT(transaction details);
else // just an action
const signedActionHex = this.signingService.signActiov(action details);
// TODO figure this out...
this.respond(id, {
signedTransactionHex,
signedActionHex,
});
*/
}
private handleJwt(data: any): void {
// Give a permission token that expires in 10 minutes. DeSo apps use it for
// things like image uploading. Creation of this token is subject to same
// access level requirements as actions and transactions.
// TODO - make this work with LBRY. Or, nix it if we know we don't need it.
// Perhaps this will actually *be* our "actions"?
if (!this.approve(data, AccessLevel.ApproveAll)) {
return;
}
@ -136,7 +134,7 @@ export class IdentityService {
private async handleInfo(event: MessageEvent): Promise<void> {
// check storage access API
let hasStorageAccess = true;
if (this.cryptoService.mustUseStorageAccess()) {
if (this.mustUseStorageAccess()) {
hasStorageAccess = await document.hasStorageAccess();
}
@ -148,57 +146,49 @@ export class IdentityService {
hasLocalStorageAccess = false;
}
// check for cookie access
this.cookieService.put('deso-test-access', 'true');
const hasCookieAccess = !!this.cookieService.get('deso-test-access');
// TODO - Sort out the storage access issue:
//
// 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
this.browserSupported = hasCookieAccess || hasLocalStorageAccess;
this.browserSupported = hasLocalStorageAccess && !this.mustUseStorageAccess();
this.respond(event.data.id, {
hasCookieAccess,
hasStorageAccess,
hasLocalStorageAccess,
browserSupported: this.browserSupported,
});
}
// Access levels
private getRequiredAccessLevel(transactionHex: string): AccessLevel {
const txBytes = new Buffer(transactionHex, 'hex');
const transaction = Transaction.fromBytes(txBytes)[0] as Transaction<any>;
switch (transaction.metadata.constructor) {
case TransactionMetadataBasicTransfer:
case TransactionMetadataBitcoinExchange:
case TransactionMetadataUpdateBitcoinUSDExchangeRate:
case TransactionMetadataCreatorCoin:
case TransactionMetadataCreatorCoinTransfer:
case TransactionMetadataSwapIdentity:
case TransactionMetadataUpdateGlobalParams:
case TransactionMetadataUpdateProfile:
case TransactionMetadataCreateNFT:
case TransactionMetadataUpdateNFT:
case TransactionMetadataAcceptNFTBid:
case TransactionMetadataNFTBid:
case TransactionMetadataNFTTransfer:
case TransactionMetadataAcceptNFTTransfer:
case TransactionMetadataBurnNFT:
case TransactionMetadataDAOCoin:
case TransactionMetadataTransferDAOCoin:
return AccessLevel.Full;
case TransactionMetadataFollow:
case TransactionMetadataPrivateMessage:
case TransactionMetadataSubmitPost:
case TransactionMetadataLike:
return AccessLevel.ApproveLarge;
}
return AccessLevel.Full;
}
// TODO implement for lbry
// private getRequiredAccessLevel(transactionHex: string): AccessLevel {
// switch case on the type of transaction or action, and return the required access level
// unless required access level is going to be case by case for each user
private hasAccessLevel(data: any, requiredAccessLevel: AccessLevel): boolean {
const { payload: { encryptedSeedHex, accessLevel, accessLevelHmac }} = data;
@ -210,25 +200,6 @@ export class IdentityService {
return this.cryptoService.validAccessLevelHmac(accessLevel, seedHex, accessLevelHmac);
}
// This method checks if transaction in the payload has correct outputs for requested AccessLevel.
private approveSpending(data: any): boolean {
const { payload: { accessLevel, transactionHex }} = data;
// If the requested access level is ApproveLarge, we want to confirm that transaction doesn't
// attempt sending $DESO to a non-owner public key. If it does, we respond with approvalRequired.
if (accessLevel === AccessLevel.ApproveLarge) {
const txBytes = new Buffer(transactionHex, 'hex');
const transaction = Transaction.fromBytes(txBytes)[0] as Transaction<any>;
for (const output of transaction.outputs) {
if (output.publicKey.toString('hex') !== transaction.publicKey.toString('hex')) {
this.respond(data.id, {approvalRequired: true});
return false;
}
}
}
return true;
}
private approve(data: any, accessLevel: AccessLevel): boolean {
const hasAccess = this.hasAccessLevel(data, accessLevel);
const hasEncryptionKey = this.cryptoService.hasSeedHexEncryptionKey(this.globalVars.hostname);

View file

@ -1,37 +0,0 @@
<app-banner></app-banner>
<div class="page-container" *ngIf="globalVars.inTab || globalVars.webview || globalVars.callback">
<div class="title-text">
Log in to {{ globalVars.hostname }}
</div>
<p class="main-text mb-20px">
Enter your DeSo seed phrase to load your account
</p>
<div *ngIf="loadSeedError" class="alert alert-danger mt-15px">
{{ loadSeedError }}
</div>
<div class="text-input-container">
<textarea [(ngModel)]="mnemonic"
class="text-input"
rows="4"
placeholder="Enter your DeSo seed phrase"></textarea>
</div>
<div class="main-text mb-20px">
If you have a passphrase, enter it below.
</div>
<div class="text-input-container">
<textarea [(ngModel)]="extraText"
class="text-input"
rows="4"
placeholder="Enter your passphrase"></textarea>
</div>
<button (click)="clickLoadAccount()"
class="button button-primary button-large">
Load Account
</button>
</div>

View file

@ -1,53 +0,0 @@
import { Component, OnInit } from '@angular/core';
import {AccountService} from '../account.service';
import {CryptoService} from '../crypto.service';
import {EntropyService} from '../entropy.service';
import {GlobalVarsService} from '../global-vars.service';
import {Router} from '@angular/router';
import {RouteNames} from '../app-routing.module';
@Component({
selector: 'app-load-seed',
templateUrl: './load-seed.component.html',
styleUrls: ['./load-seed.component.scss']
})
export class LoadSeedComponent implements OnInit {
// Loading an account
loadSeedError = '';
mnemonic = '';
extraText = '';
constructor(
private accountService: AccountService,
private cryptoService: CryptoService,
private entropyService: EntropyService,
public globalVars: GlobalVarsService,
private router: Router,
) {}
ngOnInit(): void {
}
clickLoadAccount(): void {
// Store mnemonic and extraText locally because we clear them below and otherwise
// they don't get saved in local storage reliably
const mnemonic = this.mnemonic;
const extraText = this.extraText;
const network = this.globalVars.network;
if (!this.entropyService.isValidMnemonic(mnemonic)) {
this.loadSeedError = 'Invalid mnemonic';
return;
}
const keychain = this.cryptoService.mnemonicToKeychain(mnemonic, extraText);
this.accountService.addUser(keychain, mnemonic, extraText, network);
// Clear the form
this.mnemonic = '';
this.extraText = '';
this.router.navigate(['/', RouteNames.LOG_IN], {queryParamsHandling: 'merge'});
}
}

View file

@ -0,0 +1,49 @@
<app-banner></app-banner>
<div class="container home-container text-center" *ngIf="globalVars.inTab || globalVars.webview">
<div class="mb-20px">
<span class="title-text">Choose a channel to share with {{ globalVars.hostname }}</span>
</div>
<div class="d-flex flex-column" *ngIf="hasChannels">
<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>
<div class="saved-seeds-scroll">
<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 *ngIf="item.value" class="d-flex align-items-center">
<b>{{ item.value.name }}</b>
&nbsp;
&nbsp;
<i>(#{{ item.value.claimId }})</i>
</div>
</div>
</li>
</div>
</ul>
</div>
<button class="button button-large button-secondary mb-40px" [routerLink]="['/log-in-wallet']">
<span class="font-weight-normal">Go back to Wallet Log In</span>
</button>
<div class="d-flex align-items-center flex-column">
<!--
Hmm... Maybe sign-up remains a TODO.
-->
<button class="button button-large button-secondary mb-40px" [routerLink]="['/sign-up']">
<span class="font-weight-normal">Create a new Wallet</span>
</button>
</div>
<div>
<div class="fs-18px mt-30px">
Logging in grants <b>{{ globalVars.hostname }}</b> access to your public
channel information. As you use the site, you will perform actions (such
as leaving a comment or spending LBC) that will bring you back here to
get your permission. At that point, you will have the option to give the
app permission to perform the same sort of action again without bringing
up another popup.
</div>
</div>
</div>

View file

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LogInComponent } from './log-in.component';
import { LogInAppComponent } from './log-in-app.component';
describe('LogInComponent', () => {
let component: LogInComponent;
let fixture: ComponentFixture<LogInComponent>;
describe('LogInAppComponent', () => {
let component: LogInAppComponent;
let fixture: ComponentFixture<LogInAppComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LogInComponent ]
declarations: [ LogInAppComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LogInComponent);
fixture = TestBed.createComponent(LogInAppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View file

@ -0,0 +1,41 @@
import {Component, OnInit} from '@angular/core';
import {AccountService} from '../account.service';
import {IdentityService} from '../identity.service';
import {GlobalVarsService} from '../global-vars.service';
import {PublicChannelInfo} from '../../types/identity';
@Component({
selector: 'app-log-in-app',
templateUrl: './log-in-app.component.html',
styleUrls: ['./log-in-app.component.scss']
})
export class LogInAppComponent implements OnInit {
allChannels: {[key: string]: PublicChannelInfo} = {};
hasChannels: boolean = false;
constructor(
private accountService: AccountService,
private identityService: IdentityService,
public globalVars: GlobalVarsService,
) { }
ngOnInit(): void {
this.allChannels = this.accountService.getChannelsPublic()
this.hasChannels = Object.keys(this.allChannels).length > 0
}
selectAccount(channelClaimId: string): void {
this.accountService.setAccessCurrentChannel(this.globalVars.hostname, channelClaimId)
// At this point, DeSo had globalVars.accessLevelRequest, where the app
// would specify which access level it would be operating with, and the
// user would grant permission on login. We could do something similar: The
// app could specify which sorts of actions it will be likely asking
// permission for. The user could specify on login "don't bother asking my
// permission for these actions" so they never get a popup for it.
this.identityService.login({
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
signedUp: false
});
}
}

View file

@ -0,0 +1,51 @@
<app-banner></app-banner>
<div class="page-container" *ngIf="globalVars.inTab || globalVars.webview || globalVars.callback">
<!--
<div class="title-text">
Log into the LBRY wallet sync service
</div>
<p class="main-text mb-20px">
Log in to {{ globalVars.environment.walletSyncHostname }}:
</p>
<div *ngIf="loginError" class="alert alert-danger mt-15px">
{{ loginError }}
</div>
<div class="text-input-container">
<textarea [(ngModel)]="loginUsername"
class="text-input"
rows="4"
placeholder="Username"></textarea>
</div>
<div class="text-input-container">
<textarea [(ngModel)]="loginPassword"
class="text-input"
rows="4"
placeholder="Password"></textarea>
</div>
<button (click)="clickLogin()"
class="button button-primary button-large">
Log In
</button>
<hr>
-->
<div class="title-text">
Paste your wallet directly
</div>
<p>
<textarea [value]="walletDumpInitial" id="wallet-dump" rows="20" cols="150"></textarea>
<button (click)="loginWithWalletDump()"
class="button button-primary button-large">
Log In
</button>
</p>
</div>

View file

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoadSeedComponent } from './load-seed.component';
import { LogInWalletComponent } from './log-in-wallet.component';
describe('LoadSeedComponent', () => {
let component: LoadSeedComponent;
let fixture: ComponentFixture<LoadSeedComponent>;
describe('LogInWalletComponent', () => {
let component: LogInWalletComponent;
let fixture: ComponentFixture<LogInWalletComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoadSeedComponent ]
declarations: [ LogInWalletComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoadSeedComponent);
fixture = TestBed.createComponent(LogInWalletComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View file

@ -0,0 +1,107 @@
import {BackendAPIService} from '../backend-api.service';
import {Component, OnInit} from '@angular/core';
import {AccountService} from '../account.service';
import {CryptoService} from '../crypto.service';
import {GlobalVarsService} from '../global-vars.service';
import {Router} from '@angular/router';
import {RouteNames} from '../app-routing.module';
import {of} from 'rxjs';
// This component handles two ways of logging in:
// * Wallet Sync (currently commented out)
// * Paste Wallet (temporary measure for initial version)
@Component({
selector: 'app-log-in-wallet',
templateUrl: './log-in-wallet.component.html',
styleUrls: ['./log-in-wallet.component.scss']
})
export class LogInWalletComponent implements OnInit {
walletDumpInitial = this.getWalletDumpInitial();
loginError = '';
loginUsername = '';
loginPassword = '';
constructor(
private backendApi: BackendAPIService,
private cryptoService: CryptoService,
private accountService: AccountService,
public globalVars: GlobalVarsService,
private router: Router,
) { }
ngOnInit(): void {}
// Wallet Sync (WIP, unused atm)
clickLogin() {
// Store username and password locally because we clear them below and otherwise
// they don't get saved in local storage reliably
const loginUsername = this.loginUsername;
const loginPassword = this.loginPassword;
if (loginUsername.length === 0) {
this.loginError = 'Enter a username';
return of();
}
if (loginPassword.length === 0) {
this.loginError = 'Enter a password';
return of();
}
// Returning the observable only because I'm in the habit of returning
// promises. But maybe it's not needed here.
return this.backendApi.WalletSyncLogin(loginUsername, loginPassword).subscribe(
(res => {
if (res === null) {
this.loginError = "Login Error. Try again?"
return
}
const walletStr = res.bodyJson
const walletSignature = res.signature
if (!this.cryptoService.checkSig(walletStr, walletSignature)) {
this.loginError = 'Wallet signature failed!';
return
}
const wallet = JSON.parse(res.bodyJson)
const walletError: string | null = this.cryptoService.validateWallet(wallet);
if (walletError !== null) {
this.loginError = "Wallet error: " + walletError;
return
}
this.accountService.walletLogin(wallet);
// Clear the form
this.loginUsername = '';
this.loginPassword = '';
this.router.navigate(['/', RouteNames.LOG_IN_APP], {queryParamsHandling: 'merge'});
}),
(() => {
this.loginError = "Login Error. Try again?"
})
);
}
// Paste Wallet (temporary measure for initial version)
getWalletDumpInitial() {
const wallet : object | null = this.accountService.getWallet();
return JSON.stringify(wallet, null, 2) || ""
}
loginWithWalletDump(): void {
const walletStr = (<HTMLInputElement>document.getElementById("wallet-dump")).value;
const wallet = JSON.parse(walletStr)
this.accountService.walletLogin(wallet);
this.router.navigate(['/', RouteNames.LOG_IN_APP], {queryParamsHandling: 'merge'});
}
}

View file

@ -1,58 +0,0 @@
<app-banner></app-banner>
<div class="container home-container text-center" *ngIf="globalVars.inTab || globalVars.webview">
<div class="mb-20px">
<span class="title-text">Log in to {{ globalVars.hostname }}</span>
</div>
<div class="d-flex flex-column" *ngIf="hasUsers">
<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>
<div class="saved-seeds-scroll">
<li *ngFor="let item of allUsers | keyvalue" class="list-group-item list-group-item-action cursor-pointer saved-seed" (click)="selectAccount(item.key)">
<div class="w-100">
<div *ngIf="!item.value.username" class="text-truncate">{{ item.key }}&hellip;</div>
<div *ngIf="item.value.username" class="d-flex align-items-center">
<div class="text-truncate">{{ item.value.username }}</div>
</div>
</div>
</li>
</div>
</ul>
</div>
<div class="d-flex align-items-center flex-column">
<button class="button button-large button-secondary mb-40px" [routerLink]="['/sign-up']">
<span class="font-weight-normal">Sign up with DeSo seed</span>
</button>
<a class="link" [routerLink]="['/load-seed']"><u>Log in with DeSo seed</u></a>
</div>
<div>
<div class="fs-18px mt-30px">
Logging in grants <b>{{ globalVars.hostname }}</b> access to:
</div>
<ul class="list-group list-group-flush mt-15px">
<li class="list-group-item">
<span *ngIf="globalVars.accessLevelRequest >= 2"></span>
<span *ngIf="globalVars.accessLevelRequest < 2"></span>
My basic information
<div class="fs-14px text-muted"><b>{{ globalVars.hostname }}</b> can access my public key and any other public information</div>
</li>
<li class="list-group-item">
<span *ngIf="globalVars.accessLevelRequest >= 3"></span>
<span *ngIf="globalVars.accessLevelRequest < 3"></span>
Post, message, like, and follow on my behalf
<div class="fs-14px text-muted" *ngIf="globalVars.accessLevelRequest < 3"><b>{{ globalVars.hostname }}</b> will require approval to post, message, like, and follow</div>
<div class="fs-14px text-muted" *ngIf="globalVars.accessLevelRequest >= 3"><b>{{ globalVars.hostname }}</b> may post, message, like, and follow without requiring approval</div>
</li>
<li class="list-group-item">
<span *ngIf="globalVars.accessLevelRequest === 4"></span>
<span *ngIf="globalVars.accessLevelRequest < 4"></span>
Buy, sell, and send coins on my behalf
<div class="fs-14px text-muted" *ngIf="globalVars.accessLevelRequest < 4"><b>{{ globalVars.hostname }}</b> will require approval to buy, sell, or send coins</div>
<div class="fs-14px text-muted" *ngIf="globalVars.accessLevelRequest === 4"><b>{{ globalVars.hostname }}</b> may buy, sell, and send coins without requiring approval</div>
</li>
</ul>
</div>
</div>

View file

@ -1,42 +0,0 @@
import {Component, OnInit} from '@angular/core';
import {AccountService} from '../account.service';
import {IdentityService} from '../identity.service';
import {GlobalVarsService} from '../global-vars.service';
import {BackendAPIService} from '../backend-api.service';
import {UserProfile} from '../../types/identity';
@Component({
selector: 'app-log-in',
templateUrl: './log-in.component.html',
styleUrls: ['./log-in.component.scss']
})
export class LogInComponent implements OnInit {
allUsers: {[key: string]: UserProfile} = {};
hasUsers = false;
constructor(
private accountService: AccountService,
private identityService: IdentityService,
public globalVars: GlobalVarsService,
private backendApi: BackendAPIService,
) { }
ngOnInit(): void {
// Load profile pictures and usernames
const publicKeys = this.accountService.getPublicKeys();
this.hasUsers = publicKeys.length > 0;
this.backendApi.GetUserProfiles(publicKeys)
.subscribe(profiles => {
this.allUsers = profiles;
});
}
selectAccount(publicKey: string): void {
this.accountService.setAccessLevel(publicKey, this.globalVars.hostname, this.globalVars.accessLevelRequest);
this.identityService.login({
users: this.accountService.getEncryptedUsers(),
publicKeyAdded: publicKey,
signedUp: false
});
}
}

View file

@ -1,9 +1,7 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {CryptoService} from '../crypto.service';
import {IdentityService} from '../identity.service';
import {AccountService} from '../account.service';
import {AccessLevel} from '../../types/identity';
import {GlobalVarsService} from '../global-vars.service';
@Component({
@ -20,7 +18,6 @@ export class LogoutComponent implements OnInit {
constructor(
private activatedRoute: ActivatedRoute,
private cryptoService: CryptoService,
private identityService: IdentityService,
private accountService: AccountService,
@ -28,9 +25,6 @@ export class LogoutComponent implements OnInit {
) { }
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(params => {
this.publicKey = params.publicKey || '';
});
}
onCancel(): void {
@ -39,7 +33,7 @@ export class LogoutComponent implements OnInit {
onSubmit(): void {
// We set the accessLevel for the logged out user to None.
this.accountService.setAccessLevel(this.publicKey, this.globalVars.hostname, AccessLevel.None);
this.accountService.appLogout(this.globalVars.hostname);
// We reset the seed encryption key so that all existing accounts, except
// the logged out user, will regenerate their encryptedSeedHex. Without this,
// someone could have reused the encryptedSeedHex of an already logged out user.
@ -49,7 +43,7 @@ export class LogoutComponent implements OnInit {
finishFlow(): void {
this.identityService.login({
users: this.accountService.getEncryptedUsers(),
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
});
}

View file

@ -1,6 +1,5 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {EntropyService} from '../entropy.service';
import {CryptoService} from '../crypto.service';
import {AccountService} from '../account.service';
import {IdentityService} from '../identity.service';
import {GlobalVarsService} from '../global-vars.service';
@ -19,7 +18,6 @@ export class SignUpComponent implements OnInit, OnDestroy {
seedCopied = false;
mnemonicCheck = '';
extraTextCheck = '';
publicKeyAdded = '';
// Advanced tab
showMnemonicError = false;
@ -32,7 +30,6 @@ export class SignUpComponent implements OnInit, OnDestroy {
constructor(
public entropyService: EntropyService,
private cryptoService: CryptoService,
private accountService: AccountService,
private identityService: IdentityService,
public globalVars: GlobalVarsService,
@ -81,17 +78,29 @@ export class SignUpComponent implements OnInit, OnDestroy {
////// STEP TWO BUTTONS ///////
stepTwoNext(): void {
// TODO - signup for LBRY will be very different from DeSo. We'll need to
// make a wallet, not just "users". And other things happen.
throw 'signup not implemented'
/*
// this is a mix of some of what DeSo left over, and some new LBRY.id
// specific things that I figure we won't want to forget. This is just
// a guide for the future when we tackle signup.
// TODO Don't use this.globalVars.network here, use the network specified
// in the relevant account.ledger (assuming we even really need network)
const network = this.globalVars.network;
const mnemonic = this.mnemonicCheck;
const extraText = this.extraTextCheck;
const keychain = this.cryptoService.mnemonicToKeychain(mnemonic, extraText);
this.publicKeyAdded = this.accountService.addUser(keychain, mnemonic, extraText, network);
const accountNameAdded = this.accountService.addUser(keychain, mnemonic, extraText, network);
this.accountService.setAccessLevel(
this.publicKeyAdded, this.globalVars.hostname, this.globalVars.accessLevelRequest);
this.accountService.initAccess()
this.login();
*/
}
stepTwoBack(): void {
@ -102,8 +111,7 @@ export class SignUpComponent implements OnInit, OnDestroy {
login(): void {
this.identityService.login({
users: this.accountService.getEncryptedUsers(),
publicKeyAdded: this.publicKeyAdded,
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
signedUp: true,
});
}

View file

@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import {HubService} from './hub.service';
import KeyEncoder from 'key-encoder';
import {GlobalVarsService} from './global-vars.service';
import * as jsonwebtoken from 'jsonwebtoken';
import {CryptoService} from './crypto.service';
import * as sha256 from 'sha256';
import { uvarint64ToBuf } from '../lib/bindata/util';
import {PrivateAccountInfo, Network} from '../types/identity';
import * as bip32 from 'bip32';
import { BIP32Interface } from 'bip32'; // TODO: Installed 2.0.6 instead of latest version, only because of weird typescript compilation stuff. Should probably get the latest.
@ -11,6 +11,7 @@ import * as bs58check from 'bs58check'
import * as lbry from 'bitcoinjs-lib' // TODO - package recommends browserify, which I did not do here. This works, but maybe there's good reason to browserify?
import * as ecpair from 'ecpair'; // TODO - required acorn-class-fields for this version of Angular's webpack to accept it. see extend-acorn.js.
// TODO deleteme once I remove the last use of this
const NETWORK = lbry.networks.mainnet
@Injectable({
@ -19,41 +20,389 @@ const NETWORK = lbry.networks.mainnet
export class SigningService {
constructor(
private cryptoService: CryptoService,
private hubService: HubService,
private globalVars: GlobalVarsService,
) { }
// this should be audited and go into a library. hobbled this together from
// code in bitcoinjs-lib.
private getAddressFromBip32(node: BIP32Interface): string {
const hash = lbry.crypto.hash160(node.publicKey)
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(NETWORK.pubKeyHash, 0);
hash.copy(payload, 1);
return bs58check.encode(payload);
private *generateKeys(
node: BIP32Interface,
keyPath: lbry.bip32Lbry.KeyPath
): IterableIterator<BIP32Interface> {
for (let childIndex = 0; ; childIndex++) {
yield node.derive(keyPath).derive(childIndex)
}
}
getSigningKey(wallet: any, address: string): Buffer | null {
const account = wallet.accounts
.filter((account: any) => {
let node: BIP32Interface = bip32.fromBase58(account.private_key);
return address === this.getAddressFromBip32(node)
})[0]
return bip32.fromBase58(account.private_key).privateKey || null;
// TODO - have a minimum batch size actually, like 10, that's bigger than a
// gap, to reduce number of requests to the hub. or maybe 5. Get an opinion
// on what's a common number of used keys. And channels, that may be a
// different answer.
// TODO - have a maximum number of keys? Just in case there's a bug so we
// don't blow up the user's browser?
private *generateKeyBatches(
node: BIP32Interface,
keyPath: lbry.bip32Lbry.KeyPath,
gap: number,
): IterableIterator<BIP32Interface[]> {
const generatedKeys = this.generateKeys(node, keyPath)
let batchSize = gap
while(true) {
// TODO - confirm gap off-by-one error
// Get the next `batchSize` keys
let batchKeys: BIP32Interface[] = []
for (let index = 0; index < batchSize; index++) {
batchKeys.push(generatedKeys.next().value)
}
getAddresses(wallet: any): string[] {
return wallet.accounts
// won't venture into deterministic yet
.filter((account: any) => account.address_generator.name === 'single-address')
.map((account: any) => {
let node: BIP32Interface = bip32.fromBase58(account.private_key);
return this.getAddressFromBip32(node)
// Yield them, and get back the ones that were found to be used
// TODO - if it's easy for the consumer, we can just accept the last one,
// or even better, its index within `batchKeys`
const usedBatchKeys = yield batchKeys
if (usedBatchKeys === undefined) {
throw "generateKeyBatches iteration needs an array of used batch keys"
}
// If none are used, we passed the gap and found nothing
if (usedBatchKeys.length === 0) return
// Easier for finding
const batchPubKeys = batchKeys.map(key => key.public_key)
const usedBatchPubKeys = usedBatchKeys.map(key => key.public_key)
// What is the last index of batchPubKeys (and thus batchKeys) that is in
// usedBatchPubKeys?
const lastUsedIndex = Math.max.apply(
Math, usedBatchPubKeys.map(pubKey => batchPubKeys.indexOf(pubKey))
)
// Indicates a bug here, or `usedBatchKeys` failed to be a subset of
// `batchKeys`.
if (lastUsedIndex === -1) throw "error determining next batch"
// TODO think about if this is right. Maybe needs test cases.
// Now that we know what the last used key is in our batch, we want to
// have enough keys after it to cover the next gap.
// Any keys in our current batch after the last used key is the beginning
// of the next gap.
const gapInThisBatch = batchSize - (lastUsedIndex + 1)
// The rest of the next gap will be the at the beginning of the next
// batch. We'll make our batch big enough to contain it, in case it's
// full size.
batchSize = gap - gapInThisBatch;
}
}
/*
Example claim. Just so I know the format while I'm working on this. Delete
after channel retrieval work is done.
{
"address": "bbvk6TMEzujW8r3xhP6e1FhCuSW3FYP9SJ",
"amount": "0.01",
"claim_id": "7d39c627771c529e65656f4ca86d13686acc0442",
"claim_op": "create",
"confirmations": 193,
"has_signing_key": true,
"height": 1157989,
"is_internal_transfer": false,
"is_my_input": true,
"is_my_output": true,
"is_spent": false,
"meta": {},
"name": "@lolstupidtest2",
"normalized_name": "@lolstupidtest2",
"nout": 0,
"permanent_url": "lbry://@lolstupidtest2#7d39c627771c529e65656f4ca86d13686acc0442",
"timestamp": 1652278858,
"txid": "a62878d9c558cd17f1f946df03aa8584dd002238004a02d742261b5c560dc43f",
"type": "claim",
"value": {
"public_key": "023bfe202119244b448f8974ee3152a3d859ac169a420b8ef1dda423fa015b2a4f",
"public_key_id": "bUfKnUamA7T3S2JGjV6Rmb8o3Wtn79WSTA"
},
"value_type": "channel"
}
*/
// TODO error handling?
// TODO - deprecated channel key (`certificates` field)
private getChannelsForAccount(account: PrivateAccountInfo): Observable<PrivateChannelInfo[]> {
// TODO Can single addresses register channels?
// Grin says yes.
// If so, handle single key cases. For generated key cases, assert that
// it === "deterministic-chain" (there shouldn't be anything else), or make
// a note to validate the wallet via jsonschema when reading it initially.
// TODO - there's got to be a better way.
return new Observable(subscriber => {
const keyBatches = this.generateKeyBatches(
this.bip32FromAccount(account),
lbry.bip32Lbry.KeyPath.CHANNEL,
1, // TODO what gap do I actually use for channels?
)
// Where we accumulate all of the channels we find through all of the
// recursions
let foundChannels: PrivateChannelInfo[] = []
accumulateChannels(possibleKeys: BIP32Interface[], done: boolean) {
if (done) {
// The address generator has indicated that all used channel keys
// have been found. Give the subscriber all of the channels we just
// accumulated across recursions.
subscriber.next(foundChannels)
subscriber.complete()
return
}
possibleKeysByKeyId = Object.fromEntries(
possibleKeys.map(key => [this.getAddressFromBip32(key, account.network), key])
)
this.hubService.findChannels(
possibleKeys.map(key => this.getAddressFromBip32(key, account.network))
).pipe(
map(res => {
// TODO - Should expect HUB to return revoked channels as well.
// Theose need to be added to `usedKeys` since they count toward
// gaps.
const newChannels: PrivateChannelInfo[] = res.map(hubChannel => ({
claimId: hubChannel.claim_id,
name: hubChannel.name,
normalizedName: hubChannel.normalizedName,
pubKeyId: hubChannel.value.public_key_id,
signingKey: possibleKeysByKeyId[hubChannel.value.public_key_id],
// TODO - more fields?
}))
foundChannels = foundChannels.concat(newChannels)
const usedKeys = newChannels.map(channel => possibleKeysByKeyId[channel.pubKeyId])
// Give the address generator `usedKeys` so it knows how many to grab
// next, to cover the gap we're looking for
({value: possibleKeys, done} = keyBatches.next(usedKeys));
accumulateChannels(possibleKeys, done)
})
)
}
(const {value: possibleKeys, done} = keyBatches.next());
accumulateChannels(possibleKeys, done)
})
}
/*
The functions below in the commented out block are an earlier layer of WIP
than the rest of it. At that point, we thought we needed to get all of the
used addresses and use them to get the channel claims. (Whereas the newer
plan would be to use special hub endpoints to get the channel info more
directly.)
That said, we will eventually probably have use for some of this stuff.
Primarily, we'll need to find spending keys. But who knows, maybe we'll
even change plans back again wrt Channel querying. I was afraid of deleting
it until we had something working.
It didn't get completed (or so I recall?) before we switched gears so it's
commented out. (Not that the newer plan work is completed either!)
Even if we use some or all of this, we should probably reimplement it using
`generateKeyBatches` (from the newer plan's code), because it's just
cleaner.
*/
/*
// Add all of the keys I can find from a private key
// `channelAddresses` should be all channel addresses, from both active and
// revoked claims. This helps us avoid issues with key generation gaps.
private findChannelKeysForAccount(
node: BIP32Interface,
claimChannelAddresses: string[],
deprecatedChannelKeys: {string: string}
): {[key: string]: BIP32Interface}
{
let numRemainingKeys = claimChannelAddresses.length
const channelKeys: {[key: string]: BIP32Interface} = {}
for (const channelAddress in deprecatedChannelKeys) {
if (channelAddress in claimChannelAddresses) {
// TODO - Implement getting deprecated channel private keys from pem
// format to bip32, somehow.
// channelKeys[channelAddress] = this.fromPem(deprecatedChannelKeys[channelAddress])
numRemainingKeys--;
}
}
const generatedChannelKeys = this.generateKeys(node, lbry.bip32Lbry.KeyPath.CHANNEL)
let numRemainingSkippedKeys = 10
while(numRemainingSkippedKeys>0) {
const channelKey = generatedChannelKeys.next().value
const channelAddress = this.getAddressFromBip32(channelKey, channelKey.network)
if (channelAddress in claimChannelAddresses) {
channelKeys[channelAddress] = channelKey
numRemainingKeys--;
} else {
// TODO - should this ever happen? How many should there be? Should this use the normal "gap system"?
numRemainingSkippedKeys--;
}
}
return channelKeys;
}
// TODO - Respect address use maximum, here, or wherever is relevant.
private getUsedAddresses(account: PrivateAccountInfo): {[key in lbry.bip32Lbry.KeyPath]: string[]} {
if (account.address_generator.name == "single-address") {
// TODO is this right? Can single addresses register channels?
const accountId = this.getAddress(account)
// TODO - actually not quite a "used" address either. May not have been
// used, but we should search for channels with it. So what is the right
// name?
return {RECEIVE: [accountId]} // and not have CHANGE
}
// If it is not this, something is amiss.
// TODO - actually just put this in the wallet json schema
if(account.address_generator.name !== "deterministic-chain") {
throw "Expected deterministic-chain at this point"
}
const result = {}
for (let keyPath of [lbry.bip32Lbry.KeyPath.CHANGE, lbry.bip32Lbry.KeyPath.RECEIVE]) {
const keyBatches = this.generateKeyBatches(
this.bip32FromAccount(account),
keyPath,
account.address_generator.change.gap,
)
let usedAddresses: string[] = []
let possibleKeys;
let done = false;
({value: possibleKeys, done} = keyBatches.next());
while (!done) {
const nextUsedAddresses = this.hubService.doIHaveHistory(
possibleKeys.map(key => this.getAddressFromBip32(key, key.network))
)
usedAddresses = usedAddresses.concat(nextUsedAddresses)
keyBatches.next(nextUsedAddresses)
}
result[keyPath] = usedAddresses
}
return result
}
// Query the hub for all our used addresses. Note which account they came
// from.
// return: {usedAddress: accountId}
private getAccountIDsByUsedAddress(accounts: PrivateAccountInfo[]): {[key: string]: string} {
// accountIDsByUsedAddress: {usedAddress: accountId}
const accountIDsByUsedAddress: {[key: string]: string} = {}
for (let account of accounts) {
// TODO - this.getUsedAddresses returns the addresses by keypath now
for (let usedAddress of this.getUsedAddresses(account)) {
accountIDsByUsedAddress[usedAddress] = this.getAddress(account)
}
}
}
// Query the hub for all our channel claims, based on used keys. Organize The
// channels by accountId.
// returns channelsByAccountId: {accountId: channelClaims[]}
public getChannelsByAccountId(accounts: PrivateAccountInfo[]): {[key: string]: any}{
const accountIDsByUsedAddress = this.getAccountIDsByUsedAddress(accounts)
const channelsByAccountId = {}
for (let claim of this.hubService.getClaims(Object.keys(accountIDsByUsedAddress))) {
if (claim.value_type != 'channel') {
continue
}
const accountId = accountIDsByUsedAddress[claim.address]
if(!accountId) {
// Don't trust what comes from the hub
// TODO - trust fewer things?
throw "bad accountId"
}
if (!channelsByAccountId[claim.address]) {
channelsByAccountId[claim.address] = []
}
channelsByAccountId[accountId].push(claim)
}
return channelsByAccountId
}
// Figure out channel private keys for the channel addresses, for each
// account.
// returns {accountId: {channelAddress: channelPrivateKey}}
public getChannelKeysByAccountId(accounts, channelsByAccountId): {[key: string]: {[key: string]: string}} {
const channelKeysForAccount: {[key: string]: {[key: string]: string}} = {}
for (const account of accounts) {
const accountId = this.getAddress(account)
channelKeysForAccount[accountId] = this.findChannelKeysForAccount(
this.bip32FromAccount(account),
channelsByAccountId[accountId].map(claim => claim.value.public_key_id),
account.certificates,
)
}
return channelKeysForAccount
}
*/
private bip32FromAccount(account: PrivateAccountInfo): BIP32Interface {
const network = {
'lbc_mainnet': lbry.networks.mainnet,
'lbc_testnet': lbry.networks.testnet,
'lbc_regtest': lbry.networks.regtest,
}[account.ledger]
return bip32.fromBase58(account.private_key, network);
}
// TODO - make sure this comes up with the right account ID!
public getAddress(accountKey: string, accountNetwork: Network): string {
const network = {
'lbc_mainnet': lbry.networks.mainnet,
'lbc_testnet': lbry.networks.testnet,
'lbc_regtest': lbry.networks.regtest,
}[accountNetwork]
let node: BIP32Interface = bip32.fromBase58(accountKey, network);
return this.getAddressFromBip32(node, network)
}
private getAddressFromBip32(node: BIP32Interface, network: lbry.networks.Network): string {
// taken from a test in bitcoinjs-lib
return lbry.payments.p2pkh({ pubkey: node.publicKey, network }).address!;
}
// TODO - Note to future self - I think this may be a function we only needed
// for the demo (like getSpendingAddresses). Or maybe it just needs to be
// updated for real use.
getSigningKey(wallet: any, address: string): Buffer | null {
const account = wallet.accounts
.filter((account: any) => {
return address === this.getAddress(account)
})[0]
return bip32.fromBase58(account.private_key, network).privateKey || null;
}
// Does this belong in identity.service next to getChannels? or does
// getChannels belong here next to this?
// TODO - This is outdated, but make sure there's not something I need to
// extract from here before deleting it.
/*
private getSpendingAddresses(wallet: any): string[] {
return wallet.accounts
// won't venture into deterministic yet
.filter((account: any) => account.address_generator.name === 'single-address')
.map((account: any) => this.getAddress(account))
)
}
*/
signPSBT(psbtHex: string, nonWitnessUtxoHexes: string[], signingKey: Buffer): string {
const keyPair = ecpair.ECPair.fromPrivateKey(signingKey, { network: NETWORK })
// TODO Don't use this.globalVars.network here, use the network specified
// in the relevant account.ledger (assuming we even really need network)
const keyPair = ecpair.ECPair.fromPrivateKey(signingKey, { network: this.globalVars.network })
const nonWitnessUtxos = nonWitnessUtxoHexes.map(h => Buffer.from(h, 'hex'))
const psbt = lbry.Psbt.fromHex(psbtHex)
@ -75,39 +424,29 @@ export class SigningService {
}
signJWT(seedHex: string): string {
return ""
// TODO - use bitcoinjs-lib and do an actual signature with the actual key in the wallet, send the identifier of the wallet over, etc etc etc.
// Assuming we want to keep this
const keyEncoder = new KeyEncoder('secp256k1');
const encodedPrivateKey = keyEncoder.encodePrivate(seedHex, 'raw', 'pem');
return jsonwebtoken.sign({ }, encodedPrivateKey, { algorithm: 'ES256', expiresIn: 60 * 10 });
}
signAction(seedHex: string, actionHex: string): string {
return ""
// TODO - use bitcoinjs-lib and do an actual signature with the actual key in the wallet, send the identifier of the wallet over, etc etc etc.
/*
const privateKey = this.cryptoService.seedHexToPrivateKey(seedHex);
const actionBytes = new Buffer(actionHex, 'hex');
const actionHash = new Buffer(sha256.x2(actionBytes), 'hex');
const signature = privateKey.sign(actionHash);
return new Buffer(signature.toDER()).toString('hex');
}
signTransaction(seedHex: string, transactionHex: string): string {
const privateKey = this.cryptoService.seedHexToPrivateKey(seedHex);
const transactionBytes = new Buffer(transactionHex, 'hex');
const transactionHash = new Buffer(sha256.x2(transactionBytes), 'hex');
const signature = privateKey.sign(transactionHash);
const signatureBytes = new Buffer(signature.toDER());
const signatureLength = uvarint64ToBuf(signatureBytes.length);
const signedTransactionBytes = Buffer.concat([
// This slice is bad. We need to remove the existing signature length field prior to appending the new one.
// Once we have frontend transaction construction we won't need to do this.
transactionBytes.slice(0, -1),
signatureLength,
signatureBytes,
]);
return signedTransactionBytes.toString('hex');
*/
}
}

View file

@ -1,3 +0,0 @@
<p>
<b>wallet me</b>: <textarea id="wallet" rows="20" cols="150"></textarea><button type="button" (click)="saveWallet()">Go.</button>
</p>

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestLbryLogInComponent } from './test-lbry-log-in.component';
describe('TestLbryLogInComponent', () => {
let component: TestLbryLogInComponent;
let fixture: ComponentFixture<TestLbryLogInComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TestLbryLogInComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TestLbryLogInComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,41 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { CryptoService } from '../crypto.service';
import { SigningService } from '../signing.service';
import { IdentityService } from '../identity.service';
import { GlobalVarsService } from '../global-vars.service';
@Component({
selector: 'app-test-lbry-log-in',
templateUrl: './test-lbry-log-in.component.html',
styleUrls: ['./test-lbry-log-in.component.scss']
})
export class TestLbryLogInComponent implements OnInit {
constructor(
private cryptoService: CryptoService,
private signingService: SigningService,
private identityService: IdentityService,
private globalVars: GlobalVarsService,
) { }
ngOnInit(): void {
const wallet : object | null = this.cryptoService.getWallet(this.globalVars.hostname);
(<HTMLInputElement>document.getElementById("wallet")).value = JSON.stringify(wallet, null, 2) || "";
}
saveWallet(): void {
const walletStr = (<HTMLInputElement>document.getElementById("wallet")).value;
const wallet = JSON.parse(walletStr)
this.cryptoService.putWallet(this.globalVars.hostname, wallet);
const addresses = this.signingService.getAddresses(wallet)
this.finishFlow(addresses)
}
finishFlow(addresses: string[]): void {
this.identityService.login({
users: {}, // TODO sigh
addresses,
});
}
}

View file

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core';
import {AccountService} from '../account.service';
import {Component, OnInit} from '@angular/core';
import {SigningService} from '../signing.service';
import {IdentityService} from '../identity.service';
import {CryptoService} from '../crypto.service';
import { GlobalVarsService } from '../global-vars.service';
import {GlobalVarsService} from '../global-vars.service';
@Component({
selector: 'app-test-sign-transaction',
@ -14,9 +14,9 @@ export class TestSignTransactionComponent implements OnInit {
signedTransactionHex?: string
constructor(
private accountService: AccountService,
private signingService: SigningService,
private identityService: IdentityService,
private cryptoService: CryptoService,
private globalVars: GlobalVarsService,
) { }
@ -26,7 +26,7 @@ export class TestSignTransactionComponent implements OnInit {
const fromAddress = params.get("fromAddress") || ""
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)
// TODO what if error? etc etc.
@ -39,7 +39,7 @@ export class TestSignTransactionComponent implements OnInit {
finishFlow(signedTransactionHex?: string): void {
this.identityService.login({
users: {}, // TODO sigh
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
signedTransactionHex,
});
}

View file

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core';
import {AccountService} from '../account.service';
import {Component, OnInit} from '@angular/core';
import {GlobalVarsService} from '../global-vars.service';
import {SigningService} from '../signing.service';
import {IdentityService} from '../identity.service';
@ -18,6 +20,8 @@ export class TestSignComponent implements OnInit {
privateKeyString: string = "thhUUVXQtyxonMaezCKwihLw9tZUrGJgWBMxDNfxoWub8dLGA"
constructor(
private accountService: AccountService,
private globalVars: GlobalVarsService,
private signingService: SigningService,
private identityService: IdentityService,
) { }
@ -34,7 +38,7 @@ export class TestSignComponent implements OnInit {
finishFlow(signatureHex?: string): void {
this.identityService.login({
users: {}, // TODO sigh
channel: this.accountService.getActiveChannel(this.globalVars.hostname),
signatureHex,
});
}

View file

@ -1,5 +1,5 @@
export const environment = {
production: true,
hostname: 'identity.deso.org',
nodeHostname: 'node.deso.org',
hostname: 'localhost:4201',
nodeHostname: 'node.deso.org', // TODO deleteme
};

View file

@ -4,8 +4,20 @@
export const environment = {
production: false,
hostname: 'identity.deso.org',
nodeHostname: 'node.deso.org',
hostname: 'localhost:4201',
nodeHostname: 'node.deso.org', // TODO deleteme
walletSyncHostname: 'localhost:8091',
hubHostnames: [
'spv11.lbry.com:50001',
'spv12.lbry.com:50001',
'spv13.lbry.com:50001',
'spv14.lbry.com:50001',
'spv15.lbry.com:50001',
'spv16.lbry.com:50001',
'spv17.lbry.com:50001',
'spv18.lbry.com:50001',
'spv19.lbry.com:50001',
]
};
/*

View file

@ -1,25 +1,70 @@
export interface PrivateUserInfo {
seedHex: string;
mnemonic: string;
extraText: string;
network: Network;
}
export interface PublicUserInfo {
hasExtraText: boolean;
encryptedSeedHex: string;
network: Network;
accessLevel: AccessLevel;
accessLevelHmac: string;
}
export interface UserProfile {
username: string;
// TODO: Use our wallet json-schema. Otherwise, bitcoinjs-lib probably already has a networks enum.
// TODO: what about encrypted wallets?
export enum AddressType {
DeterministicChain = "deterministic-chain",
SingleAddress = "single-address",
}
// The `ledger` field of accounts in a wallet (or PrivateAccountInfo).
// TODO - Rename this to "Ledger" to distinguish from `lbry.networks`,
// particularly since the ledger values are different from the
// `lbry.networks` field names.
export enum Network {
mainnet = 'mainnet',
testnet = 'testnet',
MainNet = "lbc_mainnet",
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,
}
export interface PrivateChannelInfo {
// TODO - add more useful stuff
claimId: string;
name: string;
normalizedName: string;
pubKeyId: string;
signingKey: string;
}
// can be sent to the app
export interface PublicChannelInfo {
// TODO - add more useful stuff
claimId: string;
name: string;
normalizedName: string;
pubKeyId: string;
// Don't care about sending the hmac-verifiable accessLevel to the app for it
// 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 {
@ -38,3 +83,9 @@ export enum AccessLevel {
// Node can sign all transactions without approval
Full = 4,
}
export enum ActionType {
Action = 0,
Transaction = 1,
// TODO - probably gets a lot more detailed than this
}