Compare commits

..

10 Commits

Author SHA1 Message Date
acd488aab0 typos 2021-05-05 10:52:10 +02:00
5378b7a820 changelog 2021-05-05 10:44:38 +02:00
1bb5d3ecb0 ios appearance and theme override 2021-05-05 10:42:37 +02:00
0d79e9c85e remove arm builds for now 2021-05-05 09:57:30 +02:00
70382a63ed slash at the end 2021-05-04 19:45:19 +02:00
b3886cc6fc add depends on 2021-05-04 19:30:25 +02:00
cfe525f274 arm images 2021-05-03 18:53:45 +02:00
96e8ec4b67 Update README.md 2021-05-03 12:38:51 +02:00
7a3397f978 always encrypt content 2021-05-03 12:21:51 +02:00
dc212d7441 docs 2021-05-03 12:21:44 +02:00
17 changed files with 197 additions and 97 deletions

View File

@@ -32,6 +32,7 @@ jobs:
id: docker_build
uses: docker/build-push-action@v2
with:
# platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
- name: Image digest

View File

@@ -5,6 +5,29 @@ 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.0.8] - 2021-05-05
### Added
- Manual theme override option
### Fixed
- Removed Arm builds for now
- iOS style bugs
## [1.0.7] - 2021-05-04
### Added
- Arm images
## [1.0.6] - 2021-05-04
### Added
- Always use encryption with random passwords included links
## [1.0.5] - 2021-05-03
### Fixed

View File

@@ -1,7 +1,7 @@
FROM node:16-alpine as CLIENT
WORKDIR /tmp
COPY ./client .
COPY ./client ./
RUN npm ci
RUN npm run build
@@ -10,7 +10,7 @@ FROM rust:1.51-alpine as RUST
WORKDIR /tmp
RUN apk add libc-dev openssl-dev alpine-sdk
COPY ./Cargo* .
COPY ./Cargo* ./
COPY ./src ./src
RUN cargo build --release

View File

@@ -8,7 +8,7 @@
## About?
_cryptgeon_ is an secure, open source sharing note service inspired by [_PrivNote_](https://privnote.com)
_cryptgeon_ is a secure, open source sharing note service inspired by [_PrivNote_](https://privnote.com)
## Demo
@@ -16,9 +16,9 @@ Check out the demo and see for yourself https://cryptgeon.nicco.io.
## Features
- view and time constrains
- server cannot decrypt contents due to client side encryption
- view and time constraints
- in memory, no persistence
- in browser encryption → server cannot decrypt contents
- obligatory dark mode support
## How does it work?
@@ -45,6 +45,8 @@ services:
app:
image: cupcakearmy/cryptgeon:latest
depends_on:
- memcache
ports:
- 80:5000
```

View File

@@ -7,29 +7,46 @@
:root {
font-family: 'Fira Mono', monospace;
--ui-bg-0: #fefefe;
--ui-bg-0-85: #fefefed9;
--ui-bg-1: #eee;
--ui-bg-2: #e2e2e2;
--ui-text-0: #111;
--ui-text-1: #222;
--ui-bg-0: #111;
--ui-bg-0-85: #111111d9;
--ui-bg-1: #333;
--ui-bg-2: #444;
--ui-text-0: #fefefe;
--ui-text-1: #eee;
--ui-clr-primary: hsl(186, 65%, 55%);
--ui-clr-error: hsl(357, 77%, 51%);
--ui-anim: all 150ms ease;
}
@media (prefers-color-scheme: dark) {
@media (prefers-color-scheme: light) {
:root {
--ui-bg-0: #111;
--ui-bg-0-85: #111111d9;
--ui-bg-1: #222;
--ui-bg-2: #282828;
--ui-text-0: #fefefe;
--ui-text-1: #eee;
--ui-bg-0: #fefefe;
--ui-bg-0-85: #fefefed9;
--ui-bg-1: #eee;
--ui-bg-2: #e2e2e2;
--ui-text-0: #111;
--ui-text-1: #333;
}
}
:root[theme='dark'] {
--ui-bg-0: #111 !important;
--ui-bg-0-85: #111111d9 !important;
--ui-bg-1: #333 !important;
--ui-bg-2: #444 !important;
--ui-text-0: #fefefe !important;
--ui-text-1: #eee !important;
}
:root[theme='light'] {
--ui-bg-0: #fefefe !important;
--ui-bg-0-85: #fefefed9 !important;
--ui-bg-1: #eee !important;
--ui-bg-2: #e2e2e2 !important;
--ui-text-0: #111 !important;
--ui-text-1: #333 !important;
}
.error-text {
color: var(--ui-clr-error);
}
@@ -61,6 +78,8 @@ input,
textarea,
button {
appearance: none;
-webkit-appearance: none;
border-radius: 0;
transition: var(--ui-anim);
font-family: inherit;
font-size: inherit;

View File

@@ -5,11 +5,10 @@ const base = axios.create({ baseURL: dev ? 'http://localhost:5000' : undefined }
export type Note = {
contents: string
password: boolean
views?: number
expiration?: number
}
export type NoteInfo = Pick<Note, 'password'>
export type NoteInfo = {}
export type NotePublic = Pick<Note, 'contents'>
export async function create(note: Note) {

View File

@@ -0,0 +1,63 @@
<script lang="ts" context="module">
import { writable } from 'svelte/store'
export enum Theme {
Dark = 'dark',
Light = 'light',
Auto = 'auto',
}
const NextTheme = {
[Theme.Auto]: Theme.Light,
[Theme.Light]: Theme.Dark,
[Theme.Dark]: Theme.Auto,
}
function init(): Theme {
if (typeof window !== 'undefined') {
const saved = window.localStorage.getItem('theme') as Theme
console.log(Theme, window.localStorage.getItem('theme'))
if (Object.values(Theme).includes(saved)) return saved
}
return Theme.Auto
}
export const theme = writable<Theme>(init())
theme.subscribe((theme) => {
if (typeof window !== 'undefined') {
window.localStorage.setItem('theme', theme)
const html = window.document.getElementsByTagName('html')[0]
html.setAttribute('theme', theme)
}
})
</script>
<script lang="ts">
import Icon from '$lib/ui/Icon.svelte'
function change() {
theme.update((current) => NextTheme[current])
}
</script>
<div on:click={change}>
<Icon class="icon" icon="contrast-sharp" />
{$theme}
</div>
<style>
div :global(.icon) {
height: 1rem;
width: 1rem;
margin-right: 0.5rem;
}
div {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
cursor: pointer;
}
</style>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type { Note } from '$lib/api'
import { create } from '$lib/api'
import { getKeyFromString, encrypt } from '$lib/crypto'
import { getKeyFromString, encrypt, Hex, getRandomBytes } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte'
import Switch from '$lib/ui/Switch.svelte'
@@ -10,11 +10,9 @@
let note: Note = {
contents: '',
password: false,
views: 1,
expiration: 60,
}
let password: string = ''
let result: { password: string; id: string } | null = null
let advanced = false
let type = false
@@ -37,18 +35,15 @@
try {
error = null
loading = true
const password = Hex.encode(getRandomBytes(32))
const key = await getKeyFromString(password)
const data: Note = {
contents: note.contents,
password: !!password,
contents: await encrypt(note.contents, key),
}
// @ts-ignore
if (type) data.expiration = parseInt(note.expiration)
// @ts-ignore
else data.views = parseInt(note.views)
if (data.password) {
const key = await getKeyFromString(password)
data.contents = await encrypt(data.contents, key)
}
const response = await create(data)
result = {
@@ -68,11 +63,12 @@
</script>
{#if result}
{#if result.password}
<TextInput type="password" readonly value={result.password} copy />
<br />
{/if}
<TextInput type="text" readonly value="{window.location.origin}/note/{result.id}" copy />
<TextInput
type="text"
readonly
value="{window.location.origin}/note/{result.id}/{result.password}"
copy
/>
<br />
<Button on:click={reset}>new</Button>
{:else}
@@ -112,15 +108,6 @@
max={360}
/>
</div>
<br />
<TextInput
type="password"
label="password"
placeholder="optional"
bind:value={password}
copy
random
/>
</div>
<style>

View File

@@ -0,0 +1,37 @@
<script lang="ts">
import Icon from '$lib/ui/Icon.svelte'
import ThemeToggle from '$lib/ui/ThemeToggle.svelte'
</script>
<footer>
<ThemeToggle />
<nav>
<a href="/">/home</a>
<a href="/about">/about</a>
<a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener">/code</a>
</nav>
</footer>
<style>
footer {
display: flex;
justify-content: space-between;
padding: 1rem;
position: fixed;
bottom: 0;
right: 0;
width: 100%;
background-color: var(--ui-bg-0-85);
}
a {
margin: 0 0.5rem;
}
nav {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
</style>

View File

@@ -8,7 +8,6 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
xmlns:serif="http://www.serif.com/"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
><g
><clipPath id="_clip1"><rect x="6.336" y="3.225" width="193.55" height="193.55" /></clipPath
@@ -78,5 +77,6 @@
width: 100%;
max-width: 16rem;
transform: translateX(-1rem);
fill: currentColor;
}
</style>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte'
import '../app.css'
@@ -13,16 +14,9 @@
<slot />
</main>
<footer>
<a href="/">/home</a>
<a href="/about">/about</a>
<a href="https://github.com/cupcakearmy/cryptgeon" target="_blank" rel="noopener">/code</a>
</footer>
<Footer />
<style>
a {
margin: 0 0.5rem;
}
main {
padding: 1rem;
padding-bottom: 4rem;
@@ -30,17 +24,4 @@
max-width: 35rem;
margin: 0 auto;
}
footer {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
padding: 1rem;
position: fixed;
bottom: 0;
right: 0;
width: 100%;
background-color: var(--ui-bg-0-85);
}
</style>

View File

@@ -13,7 +13,7 @@
<h1>About</h1>
<p>
<i>cryptgeon</i> is an secure, open source sharing note service inspired by
<i>cryptgeon</i> is a secure, open source sharing note service inspired by
<a href="https://privnote.com"><i>PrivNote</i></a>.
</p>
@@ -26,9 +26,9 @@
<b>▶ Features</b>
<ul>
<li>view and time constrains</li>
<li>server cannot decrypt contents due to client side encryption</li>
<li>view and time constraints</li>
<li>in memory, no persistence</li>
<li>in browser encryption → server cannot decrypt contents</li>
</ul>
<p>

View File

@@ -17,8 +17,8 @@
import { onMount } from 'svelte'
export let id: string
let needPassword = false
let password: string = ''
export let password: string
let note: NotePublic | null = null
let exists = false
@@ -29,8 +29,7 @@
try {
loading = true
error = null
const data = await info(id)
needPassword = data.password
await info(id)
exists = true
} catch {
exists = false
@@ -40,17 +39,15 @@
})
async function show() {
const data = note || (await get(id)) // Don't get the content twice on wrong password.
if (needPassword) {
try {
const key = await getKeyFromString(password)
data.contents = await decrypt(data.contents, key)
error = false
} catch {
error = true
}
try {
error = false
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
}
note = data
}
</script>
@@ -67,17 +64,12 @@
{:else}
<form on:submit|preventDefault={show}>
<p>click below to show and delete the note if the counter has reached it's limit</p>
{#if needPassword}
<TextInput type="password" label="password" bind:value={password} />
<br />
{/if}
<Button type="submit">show note</Button>
{#if error}
<br />
<p class="error-text">
wrong password. could not decipher.
wrong password. could not decipher. probably a broken link. note was destroyed.
<br />
note already destroyed. try again without reloading the page.
</p>
{/if}
</form>

View File

@@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Contrast</title><path d='M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z'/></svg>

After

Width:  |  Height:  |  Size: 279 B

View File

@@ -12,5 +12,7 @@ services:
app:
build: .
depends_on:
- memcached
ports:
- 80:5000

View File

@@ -5,15 +5,12 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct Note {
pub contents: String,
pub password: bool,
pub views: Option<u8>,
pub expiration: Option<u64>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct NoteInfo {
pub password: bool,
}
pub struct NoteInfo {}
#[derive(Serialize, Deserialize, Clone)]
pub struct NotePublic {

View File

@@ -23,11 +23,7 @@ async fn one(path: web::Path<NotePath>) -> impl Responder {
let note = store::get(&p.id);
match note {
None => return HttpResponse::NotFound().finish(),
Some(note) => {
return HttpResponse::Ok().json(NoteInfo {
password: note.password,
})
}
Some(_) => return HttpResponse::Ok().json(NoteInfo {}),
}
}