Commit 824f7300 authored by Cédric Moreau's avatar Cédric Moreau

[enh] Connection par trousseau

parent 1fcf2a73
......@@ -28,6 +28,7 @@ import {AuthModal} from "../components/auth_modal/auth_modal";
import {AccountPage} from "../pages/account/account";
import {ConnectPage} from "../pages/connect/connect";
import {CryptoService} from "../services/crypo-service";
import {SafeUrlPipe} from "../directives/safe-url.pipe";
const appRoutes: Routes = [
{
......@@ -92,6 +93,7 @@ const appRoutes: Routes = [
ConnectPage,
ImageCropperComponent,
ItemDetailsPage,
SafeUrlPipe,
ListPage
],
imports: [
......
......@@ -92,3 +92,10 @@ input.pubkey {
background-color: #e4ecf7;
text-align: center;
}
.upload {
input {
display: none;
}
}
<ion-content padding text-center>
<ion-content padding text-center radio-group
[(ngModel)]="connectionType"
name="type"
#type="ngModel">
<h2 text-center>Authentification</h2>
<ion-list>
<ion-item>
<ion-item class="left-radio">
<ion-label>Par fichier trousseaux de clés</ion-label>
<ion-radio value="file"></ion-radio>
</ion-item>
<ion-item class="left-radio">
<ion-label>Par identifiants secrets</ion-label>
<ion-radio value="brainwallet"></ion-radio>
</ion-item>
</ion-list>
<ion-list>
<ion-item *ngIf="connectionType == 'file'">
<ion-label>Mémoriser ce trousseau le temps de la session de navigation</ion-label>
<ion-checkbox color="royal" [(ngModel)]="remember" checked="false"></ion-checkbox>
</ion-item>
<ion-item *ngIf="connectionType == 'file'">
<div class="upload">
<input id="custom-input" type="file" (change)="fileChangeListener($event)">
<label for="custom-input" ion-button>Charger le fichier trousseau...</label>
</div>
</ion-item>
<ion-item *ngIf="connectionType == 'brainwallet'">
<ion-label>Identifiant secret</ion-label>
<ion-input type="password"
[disabled]="computing"
......@@ -12,7 +38,7 @@
placeholder="Tapez votre identifiant secret."></ion-input>
</ion-item>
<ion-item>
<ion-item *ngIf="connectionType == 'brainwallet'">
<ion-label>Mot de passe</ion-label>
<ion-input type="password"
[disabled]="computing"
......@@ -20,31 +46,31 @@
placeholder="Tapez votre mot de passe." ></ion-input>
</ion-item>
<ion-item [hidden]="generated || salt || passwd">
<ion-item *ngIf="connectionType == 'brainwallet'" [hidden]="generated || salt || passwd">
<ion-label>Tapez vos identifiants secrets puis cliquer sur « Valider ».</ion-label>
</ion-item>
<ion-item [hidden]="computing || !generated || salt || passwd">
<ion-item *ngIf="connectionType == 'brainwallet'" [hidden]="computing || !generated || salt || passwd">
<ion-label class="info">Clé : {{ generated }}</ion-label>
</ion-item>
<ion-item [hidden]="computing || !error || salt || passwd">
<ion-label class="note">{{ error }}</ion-label>
</ion-item>
<ion-item [hidden]="!computing">
<ion-item *ngIf="connectionType == 'brainwallet'" [hidden]="!computing">
<ion-spinner item-left></ion-spinner>
<ion-label>Vérification de la clé...</ion-label>
</ion-item>
<ion-item>
<ion-item *ngIf="connectionType == 'brainwallet'">
<ion-label>Mémoriser ces valeurs le temps de la navigation</ion-label>
<ion-checkbox color="royal" [(ngModel)]="remember" checked="false"></ion-checkbox>
</ion-item>
<ion-item>
<ion-item *ngIf="connectionType == 'brainwallet'">
<ion-label>Ne plus me demander confirmation le temps de la navigation</ion-label>
<ion-checkbox color="royal" [(ngModel)]="noConfirm" checked="false"></ion-checkbox>
</ion-item>
</ion-list>
<p>
<button ion-button [disabled]="computing || !(salt && passwd)" (click)="valideCle()">Valider</button>
<button ion-button [disabled]="computing || !(salt && passwd)" (click)="valideCle()" *ngIf="connectionType == 'brainwallet'">Valider</button>
<button ion-button [disabled]="computing" color="light" (click)="cancel()">Annuler</button>
</p>
</ion-content>
import {Component, OnInit, ViewChild} from "@angular/core";
import {NavParams, ViewController} from "ionic-angular";
import {CryptoService} from "../../services/crypo-service";
import {LoginService} from "../../services/login-service";
const base58 = require('../../lib/base58')
@Component({
......@@ -16,12 +17,15 @@ export class AuthModal implements OnInit {
salt:string
passwd:string
expectedPub:string
connectionType:string
computing:Boolean
remember:Boolean
noConfirm:Boolean
pair:any
constructor(
private params: NavParams,
public loginService: LoginService,
public cryptoService: CryptoService,
public viewCtrl: ViewController
) {
......@@ -30,20 +34,53 @@ export class AuthModal implements OnInit {
this.passwd = sessionStorage.getItem('passwd')
this.remember = Boolean(sessionStorage.getItem('remember'))
this.noConfirm = Boolean(sessionStorage.getItem('noConfirm'))
this.connectionType = sessionStorage.getItem('connectionType')
this.pair = this.loadMemorizedPair()
}
loadMemorizedPair() {
if (sessionStorage.getItem('publicKey')) {
return {
publicKey: base58.decode(sessionStorage.getItem('publicKey')),
secretKey: base58.decode(sessionStorage.getItem('secretKey'))
}
}
return null
}
ngOnInit(): void {
if (this.noConfirm) {
// Memorisation par salt/passwd
if (this.noConfirm && this.salt) {
this.valideCle()
}
if (!this.remember) {
// Memorisation par fichier
if (this.remember && this.pair) {
this.connectionType = 'file'
this.viewCtrl.dismiss(this.pair)
}
if (!this.remember && this.connectionType == 'brainwallet') {
setTimeout(() => {
this.saltInput.setFocus();
},150);
}
}
fileChangeListener($event) {
this.cryptoService.loadFromFile($event, true)
.then(pair => {
if (this.remember) {
sessionStorage.setItem('publicKey', base58.encode(pair.publicKey))
sessionStorage.setItem('secretKey', base58.encode(pair.secretKey))
sessionStorage.setItem('remember', "1")
}
sessionStorage.setItem('connectionType', 'file')
this.viewCtrl.dismiss(pair)
})
.catch(err => this.error = err)
}
getKeyPair() {
return this.cryptoService.getKeyPair(this.salt, this.passwd)
}
......@@ -68,13 +105,15 @@ export class AuthModal implements OnInit {
sessionStorage.removeItem('salt')
sessionStorage.removeItem('passwd')
sessionStorage.removeItem('remember')
sessionStorage.removeItem('remember')
sessionStorage.removeItem('noConfirm')
sessionStorage.removeItem('randomKeyring')
}
if (this.noConfirm) {
sessionStorage.setItem('noConfirm', "1")
} else {
sessionStorage.removeItem('noConfirm')
}
sessionStorage.setItem('connectionType', 'brainwallet')
this.viewCtrl.dismiss(pair)
} else {
this.error = 'La clé générée n\'est pas celle de votre compte. Une faute de frappe ?'
......
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({
name: 'safeUrl'
})
export class SafeUrlPipe implements PipeTransform {
constructor(private domSanitizer: DomSanitizer) {}
transform(url) {
return this.domSanitizer.bypassSecurityTrustResourceUrl(url);
}
}
......@@ -19,6 +19,11 @@
<h4>Méthode</h4>
<ion-list>
<ion-item class="left-radio">
<ion-radio value="file"></ion-radio>
<ion-label>Par fichier trousseaux de clés</ion-label>
</ion-item>
<ion-item class="left-radio">
<ion-radio value="pubkey"></ion-radio>
<ion-label>Par clé publique</ion-label>
......@@ -31,6 +36,25 @@
</ion-item>
</ion-list>
<h3>Détails</h3>
<ng-container *ngIf="connectionType == 'file'">
<p>
<ion-label class="descriptif">Permet de se connecter en fournissant le fichier contenant votre trousseau de clés.</ion-label>
</p>
<div class="upload">
<input id="custom-input" type="file" (change)="fileChangeListener($event)">
<label for="custom-input" ion-button>Charger le fichier trousseau...</label>
</div>
<p class="help">
Si vous n'avez pas encore de trousseau de clés ou que vous souhaitez en obtenir un nouveau, vous pouvez également <b>générer un nouveau trousseau</b>.
<br>
<br>
<a ion-button small download="gannonce.yml" [href]="randomKeyring | safeUrl">Générer et télécharger un nouveau trousseau</a>
</p>
</ng-container>
<ng-container *ngIf="connectionType == 'pubkey'">
<p>
<ion-label class="descriptif">C'est la méthode la plus simple et sans aucun risque : donnez une clé publique <b>que vous possédez</b> (si vous ne la possédez pas, vous ne pourrez faire aucune modification ensuite).</ion-label>
......
......@@ -33,4 +33,9 @@ connect {
.descriptif {
color: gray;
}
.help {
padding: 20px;
background-color: #f0f5ff;
}
}
......@@ -3,6 +3,7 @@ import {LoginService} from "../../services/login-service";
import {ToastController} from "ionic-angular";
import {CryptoService} from "../../services/crypo-service";
import {Router} from "@angular/router";
const seedrandom = require("seedrandom")
const base58 = require('../../lib/base58')
@Component({
......@@ -25,7 +26,7 @@ export class ConnectPage implements OnInit {
}
ngOnInit() {
this.connectionType = ""
this.connectionType = "file"
this.pub = ""
this.salt = ""
this.passwd = ""
......@@ -56,4 +57,31 @@ export class ConnectPage implements OnInit {
});
toast.present();
}
fileChangeListener($event) {
this.cryptoService.loadFromFile($event, false)
.then(pair => {
this.loginService.identify(base58.encode(pair.publicKey))
this.router.navigate([`/mon_compte`])
})
.catch(err => this.message(err))
}
get randomKeyring() {
const value = sessionStorage.getItem('randomKeyring')
if (!value) {
console.log('getRandomKeyring')
const byteseed = new Uint8Array(32)
for (let i = 0; i < 32; i++) {
const random = Math.floor(seedrandom()() * 255) + 1
byteseed[i] = random
}
const keypair = this.cryptoService.keypairFromSeed(byteseed)
const pub = base58.encode(keypair.publicKey)
const sec = base58.encode(keypair.secretKey)
const data = encodeURIComponent('pub: ' + pub + '\nsec: ' + sec)
sessionStorage.setItem('randomKeyring', "data:application/octet-stream," + data)
}
return value || sessionStorage.getItem('randomKeyring')
}
}
......@@ -56,10 +56,11 @@ export class AnnounceService {
this.ann = {
pub,
uuid: uuid.v4(),
title: '',
desc: '',
price: '',
fees: '',
title: 'zertyuiosdfghjkl',
desc: 'fzhgoigjoierĵgoêrjgoijeroigjeroijgoirejgo',
descParagraphe: 'fzhgoigjoierĵgoêrjgoijeroigjeroijgoirejgo',
price: '28',
fees: '0',
type: 'Simple',
stock: '1',
images: [
......
......@@ -2,6 +2,7 @@ import {Injectable} from "@angular/core";
const scrypt = require('scrypt-async')
const tweetnacl = require('tweetnacl')
const tweetnaclUtil = require('tweetnacl-util')
const base58 = require('../lib/base58')
@Injectable()
export class CryptoService {
......@@ -23,8 +24,39 @@ export class CryptoService {
})
.then((seed) => {
const byteseed = tweetnaclUtil.decodeBase64(seed)
return tweetnacl.sign.keyPair.fromSeed(byteseed)
return this.keypairFromSeed(byteseed)
})
}
keypairFromSeed(byteseed) {
return tweetnacl.sign.keyPair.fromSeed(byteseed)
}
loadFromFile($event, withPrivate) {
return new Promise((resolve, reject) => {
const file:File = $event.target.files[0];
const myReader:FileReader = new FileReader();
myReader.onloadend = function (loadEvent:any) {
const content = loadEvent.currentTarget.result
const pubMatch = content.match(/^pub: ([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44})\n/)
if (!pubMatch) {
return reject('Fichier incorrect : clé publique non trouvée')
}
if (!withPrivate) {
return resolve({
publicKey: base58.decode(pubMatch[1])
})
}
const secMatch = content.match(/\nsec: ([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{86,88})\n?$/)
if (!secMatch) {
return reject('Fichier incorrect : clé privée non trouvée')
}
return resolve({
publicKey: base58.decode(pubMatch[1]),
secretKey: base58.decode(secMatch[1]),
})
};
myReader.readAsText(file, 'utf8');
})
}
}
......@@ -69,6 +69,12 @@ export class LoginService {
this.pub = ''
this.estIdentifie = false
localStorage.removeItem('pub')
sessionStorage.removeItem('salt')
sessionStorage.removeItem('passwd')
sessionStorage.removeItem('remember')
sessionStorage.removeItem('publicKey')
sessionStorage.removeItem('secretKey')
sessionStorage.removeItem('connectionType')
this.cleIncorrecte("Déconnexion réussie.")
}
......
......@@ -3669,11 +3669,11 @@ pushserve@^1:
express "^4.0.0"
serve-static "^1.10.0"
qs@6.2.0:
qs@6.2.0, qs@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b"
qs@^6.2.0, qs@~6.4.0:
qs@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
......@@ -4056,6 +4056,10 @@ scrypt-async@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/scrypt-async/-/scrypt-async-1.3.1.tgz#a11fd6fac981b4b823ee01dee0221169500ddae9"
seedrandom@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.3.tgz#2438504dad33917314bff18ac4d794f16d6aaecc"
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
......@@ -4803,20 +4807,13 @@ write-file-atomic@^1.1.2:
imurmurhash "^0.1.4"
slide "^1.1.5"
ws@1.1.1:
ws@1.1.1, ws@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018"
dependencies:
options ">=0.0.5"
ultron "1.0.x"
ws@~1.1.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.4.tgz#57f40d036832e5f5055662a397c4de76ed66bf61"
dependencies:
options ">=0.0.5"
ultron "1.0.x"
xdg-basedir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment