adding and fetching docs

This commit is contained in:
Alexander Bell 2018-09-19 20:29:48 +02:00
parent d9aa8deb16
commit 8d93af6552
11 changed files with 416 additions and 270 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -10,9 +10,12 @@
<title>Ninja</title>
</head>
<body>
<!-- Alexander Bell, all rights reserved -->
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<!-- Alexander Bell, all rights reserved -->
<div id="root"></div>
<!-- Alexander Bell, all rights reserved -->
</body>
</html>

View File

@ -42,7 +42,7 @@ const App = inject("rootStore") ( observer(
const body = document.getElementsByTagName('body')[0];
body.style.margin = '0';
body.style.padding = '0';
body.style.overflow = 'hidden';
body.style.overflow = 'auto';
//Load app
this.stores.rootStore.loadApp();

View File

@ -0,0 +1,17 @@
import React from 'react';
export default function(props) {
/*
* Expected props
* - black: String
* the text in black
* - red: String
* the text in red
*/
return(
<h2 style={{padding: '20px'}}>
{props.black}<span style={{color: '#DA3821'}}>{props.red}</span>
</h2>
)
}

View File

@ -113,9 +113,7 @@ const Main = inject("rootStore") ( observer(
</Menu.Item>
</Menu.Menu>
</Menu>
<div style={{minHeight: '100vh'}}>
{this.props.children}
</div>
{this.props.children}
</div>
);
} else {
@ -123,35 +121,33 @@ const Main = inject("rootStore") ( observer(
//Mobile sidebar
return(
<div>
<Sidebar.Pushable>
<Sidebar
as={Menu}
animation='overlay'
onHide={this.handleSidebarHide}
visible={this.state.visible}
vertical
width='thin'
>
<Menu.Item header>
<img alt="" src={require('../../files/images/logo.svg')} style={{display: 'block', width: '60px', height: '45px'}} />
</Menu.Item>
<Sidebar.Pushable>
<Sidebar
as={Menu}
animation='overlay'
onHide={this.handleSidebarHide}
visible={this.state.visible}
vertical
width='thin'
>
<Menu.Item header>
<img alt="" src={require('../../files/images/logo.svg')} style={{display: 'block', width: '60px', height: '45px'}} />
</Menu.Item>
<Menu.Item name="Home" active={activeItem === 'Home'} onClick={this.handleItemClick} as='a'>
Home
</Menu.Item>
<Menu.Item name="/" active={activeItem === '/'} onClick={this.handleItemClick} as='a'>
Home
</Menu.Item>
<Menu.Item name="Password manager" active={activeItem === 'Password manager'} onClick={this.handleItemClick} as='a'>
Password manager
</Menu.Item>
</Sidebar>
<Menu.Item name="/passwords" active={activeItem === '/passwords'} onClick={this.handleItemClick} as='a'>
Password manager
</Menu.Item>
</Sidebar>
<Sidebar.Pusher dimmed={this.state.visible}>
<Icon className={this.classes.burgerButton} link color="red" size="huge" name="bars" onClick={this.handleButtonClick} />
{this.props.children}
</Sidebar.Pusher>
</Sidebar.Pushable>
</div>
<Sidebar.Pusher dimmed={this.state.visible}>
<Icon className={this.classes.burgerButton} link color="red" size="huge" name="bars" onClick={this.handleButtonClick} />
{this.props.children}
</Sidebar.Pusher>
</Sidebar.Pushable>
)
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -78,6 +78,7 @@ class Main extends Component {
<Route exact path="/" component={Home} />
<Route path="/passwords" component={PasswordManager} />
</Switch>
<img alt="" style={{width: '100px', marginBottom: '-20px', marginTop: '30px', marginLeft: '20px'}} src={require('../files/images/ninja.svg')} />
</Segment>
</Container>
</Menu>

View File

@ -1,6 +1,7 @@
import React, {Component} from 'react';
import jss from 'jss';
import preset from 'jss-preset-default';
import { Container } from 'semantic-ui-react';
/*
* Functions import
@ -9,6 +10,7 @@ import preset from 'jss-preset-default';
/*
* Component imports
*/
import Headline from '../../components/Headline';
jss.setup(preset());
@ -53,9 +55,16 @@ class Home extends Component {
render() {
return(
<div>
Home
</div>
<Container text>
<Headline black="alexbell." red="ninja" />
<p>
You are signed in on alexbell.ninja. <b>What can you do here?</b><br />
Well, I like developing software in my freetime, mainly applications
for my personal use. They might also be of advantage for you, so feel free
to browse around a little and try some of what you can find here :)
</p>
</Container>
);
}

View File

@ -1,11 +1,14 @@
import React, {Component} from 'react';
import { observer, inject } from 'mobx-react';
import jss from 'jss';
import preset from 'jss-preset-default';
import { Segment, Container, Header, Form, Input, Button, Icon } from 'semantic-ui-react';
import alertify from 'alertify.js';
/*
* Functions import
*/
import { encrypt } from '../../stores/functions/encryption';
/*
* Component imports
@ -13,6 +16,11 @@ import { Segment, Container, Header, Form, Input, Button, Icon } from 'semantic-
jss.setup(preset());
//Firebase
var firebase = require('../../stores/fb').fb;
// Initialize Cloud Firestore through Firebase
var db = firebase.firestore();
/*
###############################
@ -28,54 +36,61 @@ Components -- END
class New extends Component {
constructor(props){
super(props);
//Add a new password window
const New = inject("rootStore") ( observer(
class New extends Component {
constructor(props){
super(props);
//Add a new password window
/*
* Expected props
* - open: bool
* display component or not
* - toggleNewWindow: function
* toggles window: open and close
* - encryptionkey: String
* encryption key of the password
*/
/*
* Expected props
* - open: bool
* display component or not
* - toggleNewWindow: function
* toggles window: open and close
* - encryptionkey: String
* encryption key of the password
*/
this.state = {
url: '',
login: '',
password: this.generatePassword(),
loading: false
}
//Stored information
this.stores = this.props.rootStore.stores;
//Styles
this.styles = this.getStyles();
this.sheet = jss.createStyleSheet(this.styles);
const {classes} = this.sheet.attach();
this.classes = classes;
//Styles
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
componentWillReceiveProps(nextProps) {
if(nextProps.open !== this.props.open) {
this.setState({
this.state = {
url: '',
login: '',
password: this.generatePassword()
});
password: this.generatePassword(),
loading: false
}
//Styles
this.styles = this.getStyles();
this.sheet = jss.createStyleSheet(this.styles);
const {classes} = this.sheet.attach();
this.classes = classes;
//Styles
}
componentWillReceiveProps(nextProps) {
if(nextProps.open !== this.props.open) {
this.setState({
url: '',
login: '',
password: this.generatePassword()
});
}
}
}
componentWillUnmount() {
this.sheet.detach()
}
componentWillUnmount() {
this.sheet.detach()
}
generatePassword() {
generatePassword() {
//Creates a unique id for each chat bubble so that the animation can be triggered
var random = "";
@ -89,78 +104,111 @@ class New extends Component {
return random;
}
}
}
handleChange(e, {name}) {
this.setState({
[name]: e.target.value
});
}
render() {
if(this.props.open) {
return(
<div className={this.classes.box}>
<Container text>
<Segment padded="very">
<Form onSubmit={this.handleSubmit}>
<Header as="h2">
Add a password
</Header>
<Form.Field>
<label>Application / URL:</label>
<Input iconPosition='left' placeholder='http://exmple.com'>
<input value={this.state.url} type="text" name="url" onChange={this.handleChange} autoComplete="off" required />
</Input>
</Form.Field>
<Form.Field>
<label>Username / email adress:</label>
<Input iconPosition='left' placeholder='example@example.com'>
<input value={this.state.login} type="text" name="login" onChange={this.handleChange} autoComplete="off" required />
</Input>
</Form.Field>
<Form.Field>
<label>Password:</label>
<Input iconPosition='left' placeholder='********'>
<input value={this.state.password} type="text" name="password" onChange={this.handleChange} autoComplete="off" required />
</Input>
</Form.Field>
<p>The encryption key for this entry is '<b>{this.props.encryptionkey}</b>'</p>
<Button loading={this.state.loading} type='submit' primary>Add</Button>
<Button loading={this.state.loading} onClick={this.props.toggleNewWindow}><Icon name="x" />Cancel</Button>
</Form>
</Segment>
</Container>
</div>
);
} else {
return null;
}
}
getStyles() {
return {
box: {
width: '100vw',
height: '100vh',
position: 'fixed',
zIndex: 1000,
top: 0,
left: 0,
backgroundColor: 'rgba(255,255,255,0.9)',
paddingTop: '5%'
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
handleSubmit() {
const url = this.state.url;
const login = encrypt(this.state.login, this.props.encryptionkey);
const password = encrypt(this.state.password, this.props.encryptionkey);
const uid = this.stores.authStore.userData.uid;
db.collection("passwords").add({
uid: uid,
url: url,
login: login,
password: password,
time: new Date()
})
.then((docRef) => {
console.log("Document written with ID: ", docRef.id);
alertify.success("Document successfully added!");
this.props.toggleNewWindow();
})
.catch(function(error) {
console.error("Error adding document: ", error);
alertify.error("Something went wrong! Please check your internet connection");
this.setState({
loading: false
});
});
this.setState({
loading: true
});
}
render() {
if(this.props.open) {
return(
<div className={this.classes.box}>
<Container text>
<Segment padded="very">
<Form onSubmit={this.handleSubmit}>
<Header as="h2">
Add a password
</Header>
<Form.Field>
<label>Application / URL:</label>
<Input iconPosition='left' placeholder='http://exmple.com'>
<input value={this.state.url} type="text" name="url" onChange={this.handleChange} autoComplete="off" required />
</Input>
</Form.Field>
<Form.Field>
<label>Username / email adress:</label>
<Input iconPosition='left' placeholder='example@example.com'>
<input value={this.state.login} type="text" name="login" onChange={this.handleChange} autoComplete="off" required />
</Input>
</Form.Field>
<Form.Field>
<label>Password:</label>
<Input iconPosition='left' placeholder='********'>
<input value={this.state.password} type="text" name="password" onChange={this.handleChange} autoComplete="off" required />
</Input>
</Form.Field>
<p>The encryption key for this entry is '<b>{this.props.encryptionkey}</b>'</p>
<Button loading={this.state.loading} type='submit' primary>Add</Button>
<Button loading={this.state.loading} onClick={this.props.toggleNewWindow}><Icon name="x" />Cancel</Button>
</Form>
</Segment>
</Container>
</div>
);
} else {
return null;
}
}
getStyles() {
return {
box: {
width: '100vw',
height: '100vh',
position: 'fixed',
zIndex: 1000,
top: 0,
left: 0,
backgroundColor: 'rgba(255,255,255,0.9)',
paddingTop: '5%'
}
}
}
}
}
));
export default New;

View File

@ -1,20 +1,28 @@
import React, {Component} from 'react';
import { observer, inject } from 'mobx-react';
import jss from 'jss';
import preset from 'jss-preset-default';
import { Menu, Segment, Dropdown, Table, Button, Icon, Header } from 'semantic-ui-react';
import { Menu, Segment, Dropdown, Table, Button, Loader, Icon, Header, Input } from 'semantic-ui-react';
import alertify from 'alertify.js';
/*
* Functions import
*/
import { decrypt } from '../../stores/functions/encryption';
/*
* Component imports
*/
import New from './New';
import Headline from '../../components/Headline';
jss.setup(preset());
//Firebase
var firebase = require('../../stores/fb').fb;
// Initialize Cloud Firestore through Firebase
var db = firebase.firestore();
/*
###############################
@ -30,151 +38,201 @@ Components -- END
class PasswordManager extends Component {
constructor(props){
super(props);
//Password Manager page
const PasswordManager = inject("rootStore") ( observer(
class PasswordManager extends Component {
constructor(props){
super(props);
//Password Manager page
/*
* Expected props
* - /
*/
/*
* Expected props
* - /
*/
this.toggleNewWindow = this.toggleNewWindow.bind(this);
this.onChangeInput = this.onChangeInput.bind(this);
//Stored information
this.stores = this.props.rootStore.stores;
this.state = {
newWindowOpen: false,
key: '',
search: ''
this.toggleNewWindow = this.toggleNewWindow.bind(this);
this.onChangeInput = this.onChangeInput.bind(this);
this.state = {
newWindowOpen: false,
key: '',
search: '',
data: [],
loading: true
}
//Styles
this.styles = this.getStyles();
this.sheet = jss.createStyleSheet(this.styles);
const {classes} = this.sheet.attach();
this.classes = classes;
//Styles
}
//Styles
this.styles = this.getStyles();
this.sheet = jss.createStyleSheet(this.styles);
const {classes} = this.sheet.attach();
this.classes = classes;
//Styles
}
componentDidMount() {
this.fetchData();
}
componentWillUnmount() {
this.sheet.detach()
}
componentWillUnmount() {
this.sheet.detach()
}
onChangeInput(e) {
//Encryption key and search input
onChangeInput(e) {
//Encryption key and search input
this.setState({
[e.target.name]: e.target.value
});
}
toggleNewWindow() {
//Add new password window
if(this.state.key !== '')
{
alertify.log("Encryption key: '" + this.state.key + "'");
this.setState({
newWindowOpen: !this.state.newWindowOpen
[e.target.name]: e.target.value
});
} else {
alertify.error("Please set an encryption key!");
}
toggleNewWindow() {
//Open window to add a new password doc
if(this.state.key !== '')
{
this.setState({
newWindowOpen: !this.state.newWindowOpen
});
} else {
alertify.error("Please set an encryption key!");
}
}
fetchData() {
//Gets password list from firestore and
//Writes in this.state
const uid = this.stores.authStore.userData.uid;
db.collection("passwords").where("uid", "==", uid).orderBy("time", "desc").get().then((querySnapshot) => {
var arr = [];
querySnapshot.forEach((doc) => {
const data = doc.data();
arr.push(data);
});
this.setState({
data: arr,
loading: false
});
});
}
displayData(dataList) {
//Returns array with all table row components
var components = [];
dataList.forEach((element, index) => {
components.push(
<Table.Row key={index}>
<Table.Cell>{element.url}</Table.Cell>
<Table.Cell>{decrypt(element.login, this.state.key)}</Table.Cell>
<Table.Cell>
<Button icon>
<Icon name='copy' />
</Button>
</Table.Cell>
<Table.Cell>{decrypt(element.password, this.state.key)}</Table.Cell>
<Table.Cell>
<Button icon>
<Icon name='copy' />
</Button>
</Table.Cell>
</Table.Row>
);
});
return components;
}
render() {
const tableRows = this.displayData(this.state.data)
return(
<div>
<New encryptionkey={this.state.key} open={this.state.newWindowOpen} toggleNewWindow={this.toggleNewWindow} />
<Headline black="Password " red="manager" />
<Input icon placeholder='Search...'>
<input type='text' placeholder='Search...' name="search" onChange={this.onChangeInput} autoComplete="off" />
<Icon name='search' />
</Input>
<Menu attached='top'>
<Dropdown item icon='wrench' simple>
<Dropdown.Menu>
<Dropdown.Item onClick={this.toggleNewWindow}>
New
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Menu.Menu position='right'>
<div className='ui right aligned category search item'>
<div className='ui transparent icon input'>
<input className='prompt' type='text' placeholder='Encryption key...' name="key" onChange={this.onChangeInput} autoComplete="off" />
<i className='key icon' />
</div>
</div>
</Menu.Menu>
</Menu>
<Segment attached='bottom'>
<Table celled striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={4}>Application / URL</Table.HeaderCell>
<Table.HeaderCell width={5}>Username / Email adress</Table.HeaderCell>
<Table.HeaderCell width={1}>Copy</Table.HeaderCell>
<Table.HeaderCell width={5}>Password</Table.HeaderCell>
<Table.HeaderCell width={1}>Copy</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{tableRows}
</Table.Body>
</Table>
<Loader active={this.state.loading} />
</Segment>
<Header as="h3">
How does it work? Is it safe?
</Header>
<p>
I ask you to give an encryption key. Once you set that key you can create a new password which is then encrypted with that key.
</p>
<p>
The password is then saved in the encrypted form on the database in the internet, which means that nobody (not even me) can read your password unless that person knows the key. In other words: <b>The password is completely safe and solely accessible by you and only you!</b>
</p>
<p>
To decrypt your passwords simply type in the encryption key and all your passwords in your list are displayed correctly and readable in the form you had set them.
</p>
<hr />
<p>
<b>I advise you to choose one encryption key and use it for all your passwords in your list.</b> Why? Using the same key makes decrypting easier for you because <b>all</b> entries are being decrypted correctly at the same time by using one single key.
</p>
</div>
);
}
getStyles() {
return {
}
}
}
render() {
return(
<div>
<New encryptionkey={this.state.key} open={this.state.newWindowOpen} toggleNewWindow={this.toggleNewWindow} />
<Menu attached='top'>
<Dropdown item icon='wrench' simple>
<Dropdown.Menu>
<Dropdown.Item onClick={this.toggleNewWindow}>
New
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Menu.Menu position='right'>
<div className='ui right aligned category search item'>
<div className='ui transparent icon input'>
<input className='prompt' type='text' placeholder='Encryption key...' name="key" onChange={this.onChangeInput} autoComplete="off" />
<i className='key icon' />
</div>
</div>
<div className='ui right aligned category search item'>
<div className='ui transparent icon input'>
<input className='prompt' type='text' placeholder='Search...' name="search" onChange={this.onChangeInput} autoComplete="off" />
<i className='search icon' />
</div>
</div>
</Menu.Menu>
</Menu>
<Segment attached='bottom'>
<Table celled striped>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={4}>Application / URL</Table.HeaderCell>
<Table.HeaderCell width={5}>Username / Email adress</Table.HeaderCell>
<Table.HeaderCell width={1}>Copy</Table.HeaderCell>
<Table.HeaderCell width={5}>Password</Table.HeaderCell>
<Table.HeaderCell width={1}>Copy</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>cell</Table.Cell>
<Table.Cell>cell</Table.Cell>
<Table.Cell>
<Button icon>
<Icon name='copy' />
</Button>
</Table.Cell>
<Table.Cell>cell</Table.Cell>
<Table.Cell>
<Button icon>
<Icon name='copy' />
</Button>
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</Segment>
<Header as="h3">
How does it work?
</Header>
<p>
I ask you to give an encryption key. Once you set that key you can create a new password which is then encrypted with that key.
</p>
<p>
The password is then saved in the encrypted form on the database in the internet, which means that nobody (not even me) can read your password unless that person knows the key. In other words: <b>The password is completely safe and solely accessible by you and only you!</b>
</p>
<p>
To decrypt your passwords simply type in the encryption key and all your passwords in your list are displayed correctly and readable in the form you had set them.
</p>
<hr />
<p>
I advise you to choose one encryption key and use it for all your passwords in your list. Why? Using the same key makes decrypting easier for you because <b>all</b> entries are being decrypted correctly at the same time by using one single key.
</p>
</div>
);
}
getStyles() {
return {
}
}
}
));
export default PasswordManager;

View File

@ -0,0 +1,13 @@
function norm_to_ascii(string){return unescape(encodeURIComponent(string))};
function norm_to_unicode(string){return decodeURIComponent(escape(string))};
function crypt_sym(string,k){return String.fromCharCode.apply(undefined,string.split("").map(function(c){return c.charCodeAt(0)^(k||13)}))};
export function encrypt(string, key) {
//Encrypt a string
return btoa(crypt_sym(norm_to_ascii(string), key));
}
export function decrypt(string, key) {
//Decrypt a string
return crypt_sym(norm_to_unicode(atob(string)), key);
}