Compare commits

...

21 Commits

Author SHA1 Message Date
24f9aeb229 arm images 2021-05-19 11:29:33 +02:00
976413e11b limit min height 2021-05-17 09:45:01 +02:00
2480d875b4 about page 2021-05-16 12:47:52 +02:00
5dff12ea70 use hash instead of path for key 2021-05-16 11:16:25 +02:00
e332dc63e8 on push 2021-05-14 15:09:55 +02:00
a18e9bcc88 Merge pull request #7 from cupcakearmy/testing
use docker-compose
2021-05-14 14:48:56 +02:00
4b43edf54a use docker-compose 2021-05-14 14:48:34 +02:00
e3aa2dd5ff Update test.yml 2021-05-10 10:13:05 +02:00
98a03c25e6 Merge pull request #6 from cupcakearmy/testing
Testing
2021-05-10 10:11:59 +02:00
7f618e7e45 ci 2021-05-10 10:11:31 +02:00
84a7be4549 config 2021-05-10 10:04:19 +02:00
b2bad5f64c cypress runner 2021-05-10 09:58:21 +02:00
41f55c0920 test ids 2021-05-10 09:58:13 +02:00
edbf8a8ecf changelog 2021-05-08 21:47:13 +02:00
4852804581 time bug 2021-05-08 21:47:08 +02:00
22b1c35b3e loading state 2021-05-08 21:46:52 +02:00
d1e9ffd89b up the iterations 2021-05-08 21:46:43 +02:00
9c675ba48c notes about availability 2021-05-08 21:46:33 +02:00
ef3d3d5bde changelog 2021-05-08 10:34:18 +02:00
7e835af3f2 changelog 2021-05-08 10:17:20 +02:00
f153102978 bug 2021-05-08 10:16:05 +02:00
18 changed files with 4757 additions and 39 deletions

View File

@@ -1 +1,2 @@
target
node_modules

View File

@@ -14,6 +14,8 @@ jobs:
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
install: true
- name: Docker Labels
id: meta
uses: crazy-max/ghaction-docker-meta@v2
@@ -32,7 +34,7 @@ jobs:
id: docker_build
uses: docker/build-push-action@v2
with:
# platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
- name: Image digest

18
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: test
on:
workflow_dispatch:
push:
jobs:
text:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install
run: |
docker-compose build
npm ci
- name: Test
run: npm run test:run

View File

@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.1] - 2021-05-17
### Fixed
- Height on big displays
- About page
## [1.1.0] - 2021-05-16
### Security
- Using hash `#` instead of path
## [1.0.11] - 2021-05-08
### Added
- loading text
- description for created notes about availability
### Changed
- iterations from 100 to 100k
### Fixed
- time based view bug
## [1.0.10] - 2021-05-08
### Fixed
- API endpoint was not reachable
## [1.0.9] - 2021-05-07
## Changed

View File

@@ -23,4 +23,6 @@ COPY --from=CLIENT /tmp/build ./client/build
ENV MEMCACHE=memcached:11211
EXPOSE 5000
ENTRYPOINT [ "/app/cryptgeon" ]

View File

@@ -13,7 +13,7 @@ type CallOptions = {
method: string
body?: any
}
const base = dev ? 'http://localhost:5000' : undefined
const base = dev ? 'http://localhost:5000/api/' : '/api/'
async function call(options: CallOptions) {
return fetch(base + options.url, {
method: options.method,
@@ -27,7 +27,7 @@ async function call(options: CallOptions) {
export async function create(note: Note) {
const data = await call({
url: '/api/notes',
url: 'notes',
method: 'post',
body: note,
})
@@ -36,7 +36,7 @@ export async function create(note: Note) {
export async function get(id: string) {
const data = await call({
url: `/api/notes/${id}`,
url: `notes/${id}`,
method: 'delete',
})
return data as NotePublic
@@ -44,7 +44,7 @@ export async function get(id: string) {
export async function info(id: string) {
const data = await call({
url: `/api/notes/${id}`,
url: `notes/${id}`,
method: 'get',
})
return data as NoteInfo

View File

@@ -36,7 +36,7 @@ export function getKeyFromString(password: string) {
}
export async function getDerivedForKey(key: CryptoKey, salt: ArrayBuffer) {
const iterations = 1_000
const iterations = 100_000
return window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',

View File

@@ -13,7 +13,7 @@
<style>
textarea {
width: 100%;
min-height: calc(100vh - 30rem);
min-height: min(calc(100vh - 30rem), 30rem);
margin: 0;
border: 2px solid var(--ui-bg-1);
resize: vertical;

View File

@@ -50,7 +50,8 @@
password: password,
id: response.id,
}
} catch {
} catch (e) {
console.error(e)
error = 'could not create note.'
} finally {
loading = false
@@ -66,26 +67,49 @@
<TextInput
type="text"
readonly
value="{window.location.origin}/note/{result.id}/{result.password}"
label="share link"
value="{window.location.origin}/note/{result.id}#{result.password}"
copy
data-testid="note-share-link"
/>
<br />
<Button on:click={reset}>new</Button>
<p>
<b>availability:</b>
<br />
the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes
will be removed.
<br />
(you probably will be fine, just be warned.)
</p>
<br />
<Button on:click={reset}>new note</Button>
{:else}
<form on:submit|preventDefault={submit}>
<fieldset disabled={loading}>
<TextArea label="note" bind:value={note.contents} placeholder="..." />
<TextArea
label="note"
bind:value={note.contents}
placeholder="..."
data-testid="input-note"
/>
<div class="bottom">
<Switch label="advanced" bind:value={advanced} />
<Button type="submit">create</Button>
<Button type="submit" data-testid="button-create">create</Button>
</div>
{#if error}
<div class="error-text">{error}</div>
{/if}
<p><br />{message}</p>
<p>
<br />
{#if loading}
loading...
{:else}
{message}
{/if}
</p>
<div class="advanced" class:hidden={!advanced}>
<br />

View File

@@ -20,11 +20,13 @@
<p>
<b>▶ how does it work?</b>
<br />
each note has a 512bit generated <i>id</i> that is used to retrieve the note. data is stored in memory
and never persisted to disk.
each note has a 512bit generated <i>id</i> that is used to retrieve the note. the note is then encrypted
with aes in gcm mode on the client side and then sent to the server. data is stored in memory and
never persisted to disk. the server never sees the encryption key and cannot decrypt the contents
of the notes even if it tried to.
</p>
<b>Features</b>
<b>features</b>
<ul>
<li>server cannot decrypt contents due to client side encryption</li>
<li>view and time constraints</li>

View File

@@ -7,18 +7,17 @@
</script>
<script lang="ts">
import { onMount } from 'svelte'
import copy from 'copy-to-clipboard'
import type { NotePublic } from '$lib/api'
import { info, get } from '$lib/api'
import { decrypt, getKeyFromString } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte'
import TextInput from '$lib/ui/TextInput.svelte'
import copy from 'copy-to-clipboard'
import { onMount } from 'svelte'
export let id: string
export let password: string
let password: string
let note: NotePublic | null = null
let exists = false
@@ -29,6 +28,8 @@
try {
loading = true
error = null
password = window.location.hash.slice(1)
console.log(password)
await info(id)
exists = true
} catch {
@@ -41,40 +42,50 @@
async function show() {
try {
error = false
loading = true
const data = note || (await get(id)) // Don't get the content twice on wrong password.
const key = await getKeyFromString(password)
data.contents = await decrypt(data.contents, key)
note = data
} catch {
error = true
} finally {
loading = false
}
}
</script>
{#if !loading}
{#if !exists}
<p class="error-text">note was not found or was already deleted.</p>
<p class="error-text" data-testid="note-not-found">
note was not found or was already deleted.
</p>
{:else if note && !error}
<p class="error-text">you will not get the chance to see the note again.</p>
<div class="note">
<div class="note" data-testid="note-result">
{note.contents}
</div>
<br />
<Button on:click={() => copy(note.contents)}>copy to clipboard</Button>
{:else}
<form on:submit|preventDefault={show}>
<p>click below to show and delete the note if the counter has reached it's limit</p>
<Button type="submit">show note</Button>
{#if error}
<br />
<p class="error-text">
wrong password. could not decipher. probably a broken link. note was destroyed.
<fieldset>
<p>click below to show and delete the note if the counter has reached it's limit</p>
<Button type="submit" data-testid="button-show">show note</Button>
{#if error}
<br />
</p>
{/if}
<p class="error-text">
wrong password. could not decipher. probably a broken link. note was destroyed.
<br />
</p>
{/if}
</fieldset>
</form>
{/if}
{/if}
{#if loading}
<p>loading...</p>
{/if}
<style>
.note {

5
cypress.json Normal file
View File

@@ -0,0 +1,5 @@
{
"fixturesFolder": false,
"pluginsFile": false,
"supportFile": false
}

2
cypress/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
screenshots
videos

View File

@@ -0,0 +1,41 @@
function createNote(options = {}) {
Object.assign(options, {
text: `Revaluation battle selfish derive suicide revaluation society love superiority salvation spirit virtues revaluation. Aversion sexuality play burying mountains intentions battle reason strong burying war insofar inexpedient war. Fearful intentions selfish madness suicide.`,
})
cy.visit('http://localhost:5000')
const text = options.text
cy.get('[data-testid=input-note]').type(text)
cy.get('[data-testid=button-create]').click()
cy.wait(500)
return cy
.get('[data-testid=note-share-link]')
.invoke('val')
.then((link) => {
return [link, text]
})
}
describe('Basics', () => {
it('Share note', () => {
createNote().then(([link, text]) => {
cy.visit(link)
cy.get('[data-testid=button-show]').click()
cy.wait(250)
cy.get('[data-testid=note-result]').should('have.text', text)
})
})
it('Check destroyed', () => {
createNote().then(([link, text]) => {
// Check the first time
cy.visit(link)
cy.get('[data-testid=button-show]').click()
cy.wait(250)
cy.get('[data-testid=note-result]').should('have.text', text)
// Should not exists anymore
cy.visit(link)
cy.get('[data-testid=note-not-found]').should('exist')
})
})
})

View File

@@ -15,4 +15,4 @@ services:
depends_on:
- memcached
ports:
- 80:5000
- 5000:5000

4572
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,14 @@
"dev:docker": "docker-compose up memcached",
"dev:backend": "cargo watch -x 'run --bin cryptgeon'",
"dev:front": "npm --prefix client run dev",
"dev": "run-p dev:*"
"dev": "run-p dev:*",
"test:server": "docker-compose up --build",
"test:cypress": "cypress run --headless",
"test:run": "start-server-and-test test:server http://localhost:5000 test:cypress"
},
"devDependencies": {
"npm-run-all": "^4.1.5"
"cypress": "^7.2.0",
"npm-run-all": "^4.1.5",
"start-server-and-test": "^1.12.1"
}
}

View File

@@ -56,7 +56,8 @@ async fn create(note: web::Json<Note>) -> impl Responder {
if e > 360 {
return bad_req;
}
n.expiration = Some(now() + (e * 60))
let expiration = now() + (e * 60);
n.expiration = Some(expiration);
}
_ => {}
}
@@ -89,8 +90,8 @@ async fn delete(path: web::Path<NotePath>) -> impl Responder {
}
match changed.expiration {
Some(e) => {
if e > now() {
store::del(&p.id.clone());
store::del(&p.id.clone());
if e < now() {
return HttpResponse::BadRequest().finish();
}
}