Skip to content

Commit

Permalink
use seperate file for crypto, add Diff Editor, added LICENSE, added R…
Browse files Browse the repository at this point in the history
…epo URL, slight refactorings
  • Loading branch information
cs8898 committed Jan 10, 2024
1 parent 0230148 commit d7cac36
Show file tree
Hide file tree
Showing 16 changed files with 1,156 additions and 143 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules

/.storage
/.docu

.gitignore
README.md
Binary file added .docu/franz_diff.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@

## ![Monaco Franze Image](/public/favicon.ico)

> a monaco based Pastebin alternative
> a [monaco](https://github.com/microsoft/monaco-editor) based Pastebin alternative
## Features

* AES-GCM encryption
* Language Model Switcher
* Diff Editor

## Diff Editor

![Monaco Franze Image](/.docu/franz_diff.png)

The DiffEditor is available by prepending `/diff` to the URL path.

`http://localhost:3000` => `http://localhost:3000/diff`
`http://localhost:3000/fooBar#someJWK` => `http://localhost:3000/diff/fooBar#someJWK`
5 changes: 5 additions & 0 deletions app/router.options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
scrollBehavior() {
return { top: 0 }
},
}
8 changes: 7 additions & 1 deletion assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ body,
justify-content: flex-start;

button {
height: 100%;
outline: none;
background: transparent;
border: none;
Expand All @@ -72,6 +73,11 @@ body,
background-color: var(--input-hover-background);
}

a {
text-decoration: none;
box-sizing: border-box;
}

span {
margin-top: auto;
margin-bottom: auto;
Expand Down Expand Up @@ -135,4 +141,4 @@ body,

.saved {
fill: var(--font-color);
}
}
95 changes: 95 additions & 0 deletions components/MonacoDiffEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<div class="editor" ref="container"></div>
</template>

<script setup lang="ts">
import * as monaco from 'monaco-editor';
let container = ref<HTMLDivElement | null>(null);
let editor: monaco.editor.IStandaloneDiffEditor | null = null;
let modelOriginal: monaco.editor.ITextModel | null = null;
let modelModified: monaco.editor.ITextModel | null = null;
useHead({
title: 'Franz! Diff'
});
const emit = defineEmits<{
(e: "fileChanged"): void,
(e: "inited"): void
}>();
const props = defineProps<{
language: string,
original?: string,
modified?: string
}>();
var ignoreChanges = true;
onMounted(() => {
if (process.client) {
if(!container.value){
console.error("Container is missing", container.value);
return;
}
editor = monaco.editor.createDiffEditor(container.value!, {
theme: 'vs-dark',
automaticLayout: true,
originalEditable: true
});
modelOriginal = monaco.editor.createModel(props.original || "", props.language);
modelModified = monaco.editor.createModel(props.modified || "", props.language);
editor.setModel({
original: modelOriginal!,
modified: modelModified!
});
editor.onDidChangeModel((_e) => {
if (!ignoreChanges)
emit("fileChanged");
});
editor.focus();
ignoreChanges = false;
emit("inited");
}
});
watch(props, (val, old) => {
if (modelOriginal!.getLanguageId() != val.language) {
monaco.editor.setModelLanguage(modelOriginal!, val.language);
monaco.editor.setModelLanguage(modelModified!, val.language);
}
if (val.original && val.original !== old.original) {
ignoreChanges = true;
modelOriginal!.setValue(val.original)
ignoreChanges = false;
}
if (val.modified && val.modified !== old.modified) {
ignoreChanges = true;
modelModified!.setValue(val.modified)
ignoreChanges = false;
}
});
function getSource(): {original: string, modified: string} {
return {
original: modelOriginal!.getValue(),
modified: modelModified!.getValue()
};
}
function setSource(original: string, modified: string) {
ignoreChanges = true;
modelOriginal!.setValue(original);
modelModified!.setValue(modified);
ignoreChanges = false;
}
defineExpose({
getSource,
setSource
})
</script>
6 changes: 6 additions & 0 deletions components/SaveIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512">
<path
d="M433.9 129.9l-83.9-83.9A48 48 0 0 0 316.1 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V163.9a48 48 0 0 0 -14.1-33.9zM224 416c-35.3 0-64-28.7-64-64 0-35.3 28.7-64 64-64s64 28.7 64 64c0 35.3-28.7 64-64 64zm96-304.5V212c0 6.6-5.4 12-12 12H76c-6.6 0-12-5.4-12-12V108c0-6.6 5.4-12 12-12h228.5c3.2 0 6.2 1.3 8.5 3.5l3.5 3.5A12 12 0 0 1 320 111.5z" />
</svg>
</template>
7 changes: 7 additions & 0 deletions components/VersionLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<a :href="$config.public.repository" target="_blank">
<button>
v{{ $config.public.version }}
</button>
</a>
</template>
34 changes: 17 additions & 17 deletions model/DummyMonaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,26 @@ const monacoFace = `


const quotes = [
`Komisch, ge?\nDa kannst krank sein wiest mogst und immer noch lockt das Weib.`,
`"Wo is denn out momentan?"\n"Goetheplatz."`,
`Ein rechter Scheißdreck war's!\nAltmodisch bis provinziell war's!\nDes war's!`,
`Ich wollte Sie ja gar nicht ansprechen, Fräulein.\nIch wollte Sie ja nur fragen,\nob wir vielleicht eine Tasse Kaffe zusammen trinken wollen.`,
`Aus is und gar is, und schad is, dass's wahr is!`,
`Wie meinst Krise, Spatzl?`,
`Geh Spatzl, schau wie I schau!\nRecht viel treuer schaut auch kein Schaf.`,
`Bussi, Uschi!`,
`Spatzl, es gibt Sachen im Leben und besonders im Fasching, die wenn man's nicht selber erlebt hat, glaubt man's fast selbst nicht.`,
`Ehrlich gesagt, ich interessiere mich wahnsinnig für Frauen!`,
`A bissel was geht immer!`
`Komisch, ge?\nDa kannst krank sein wiest mogst und immer noch lockt das Weib.`,
`"Wo is denn out momentan?"\n"Goetheplatz."`,
`Ein rechter Scheißdreck war's!\nAltmodisch bis provinziell war's!\nDes war's!`,
`Ich wollte Sie ja gar nicht ansprechen, Fräulein.\nIch wollte Sie ja nur fragen,\nob wir vielleicht eine Tasse Kaffe zusammen trinken wollen.`,
`Aus is und gar is, und schad is, dass's wahr is!`,
`Wie meinst Krise, Spatzl?`,
`Geh Spatzl, schau wie I schau!\nRecht viel treuer schaut auch kein Schaf.`,
`Bussi, Uschi!`,
`Spatzl, es gibt Sachen im Leben und besonders im Fasching, die wenn man's nicht selber erlebt hat, glaubt man's fast selbst nicht.`,
`Ehrlich gesagt, ich interessiere mich wahnsinnig für Frauen!`,
`A bissel was geht immer!`
];

function getRandomQuote() {
const i = Math.floor(Math.random() * quotes.length);
return quotes[i];
const i = Math.floor(Math.random() * quotes.length);
return quotes[i];
}

export function getPlaceholderText(): string{
const quote = getRandomQuote();
const replaced = quote.replaceAll("\n", "\n> ");
return pre + replaced + "\n\n```raw\n" + monacoFace + "```\n";
export function getPlaceholderText(quote?: string): string {
const finalQuote = quote || getRandomQuote();
const replaced = finalQuote.replaceAll("\n", "\n> ");
return pre + replaced + "\n\n```raw\n" + monacoFace + "```\n";
}
7 changes: 7 additions & 0 deletions model/Paste.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default interface Paste {
id?: string,
content: string,
modified?: string,
language: string,
iv?: string
}
157 changes: 157 additions & 0 deletions model/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import type Paste from "./Paste";

export async function decryptPaste(key: CryptoKey | string, paste?: Paste): Promise<Paste | null> {
if (!process.client) {
return null;
}
var currentKey: CryptoKey;
const subtle = window.crypto.subtle;
if (typeof key === "string") {
console.log("Key provided as String", key);
currentKey = await decodeJWK(key as string);
console.log("Decoded Key to CryptoKey", currentKey);
} else if (key instanceof CryptoKey) {
currentKey = key;
} else {
throw new Error(`Unsupported Type for argument Key "${typeof key}"`);
}

if (!paste) {
throw new Error("No Paste provided");
}


if(!paste.iv || !paste.content){
throw new Error("Malformed Encrypted Paste");
}

console.log("Decrypt IV", paste.iv)
const iv = base64ToBytes(paste.iv!);

const contentCipher = base64ToBytes(paste.content);
const contentDecrypted = await subtle.decrypt({
name: 'AES-GCM',
iv: iv
}, currentKey, contentCipher);

const decoder = new TextDecoder();
const content = decoder.decode(contentDecrypted);
var modified = "";
if (paste.modified) {
const modifiedCipher = base64ToBytes(paste.modified);
const modifiedDecrypted = await subtle.decrypt({
name: 'AES-GCM',
iv: iv
}, currentKey, modifiedCipher);

const modifiedDecoder = new TextDecoder();
modified = modifiedDecoder.decode(modifiedDecrypted);
}

const decryptedPaste: Paste = {
id: paste.id,
content: content,
modified: modified,
language: paste.language
};
return decryptedPaste;
}

export function extractKeyFromHash(): string | undefined {
const encodedKey = window.location.hash.slice(1);
if (encodedKey !== "") {
return encodedKey;
}
return undefined;
}

export async function encryptPaste(paste: { id?: string, content: string, modified?: string, language: string }, key?: CryptoKey | string) {
if (!process.client) {
return null;
}

const subtle = window.crypto.subtle;

var currentKey: CryptoKey;
if (typeof key === "string") {
currentKey = await decodeJWK(key as string);
} else if (key instanceof CryptoKey) {
currentKey = key;
} else {
currentKey = await generateNewKey();
}

const iv = window.crypto.getRandomValues(new Uint8Array(12));
const base64IV = bytesToBase64(iv);
console.log("Encrypt IV", iv, base64IV);

const contentEncoder = new TextEncoder();
const contentEncoded = contentEncoder.encode(paste.content);
const contentCipher = await window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv: iv,
}, currentKey, contentEncoded);
const contentBase64Encrypted = bytesToBase64(new Uint8Array(contentCipher));


var modifiedBase64Encrypted: string | undefined = undefined;
if (paste.modified) {
const modifiedEncoder = new TextEncoder();
const modifiedEncoded = modifiedEncoder.encode(paste.modified);
const modifiedCipher = await window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv: iv,
}, currentKey, modifiedEncoded);
modifiedBase64Encrypted = bytesToBase64(new Uint8Array(modifiedCipher));
}

const postBody = {
id: paste.id,
iv: base64IV,
content: contentBase64Encrypted,
modified: modifiedBase64Encrypted,
language: paste.language,
};

return postBody;
}

async function decodeJWK(key: string): Promise<CryptoKey> {
console.log("decodeJwk", key)
const subtle = window.crypto.subtle;
const jwk: JsonWebKey = {
alg: "A256GCM",
ext: true,
k: key,
key_ops: ["encrypt", "decrypt"],
kty: "oct"
}
/* @ts-ignore jwk is unknown or does not match some type */
return await subtle.importKey("jwk", jwk, {
name: 'AES-GCM'
} as AesKeyAlgorithm, jwk.ext, jwk.key_ops);
}

async function generateNewKey(): Promise<CryptoKey> {
const subtle = window.crypto.subtle;
const key = await subtle.generateKey({
name: 'AES-GCM',
length: 256
}, true, ['encrypt', 'decrypt']);
const jwkOut = await subtle.exportKey('jwk', key);
location.hash = `#${jwkOut.k}`;
return key;
}

// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem.
export function base64ToBytes(base64: string): Uint8Array {
const binString = atob(base64);
//@ts-ignore fooBar
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}

// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem.
export function bytesToBase64(bytes: Uint8Array): string {
const binString = String.fromCodePoint(...bytes);
return btoa(binString);
}
3 changes: 2 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export default defineNuxtConfig({
},
runtimeConfig: {
public: {
version: pkg.version
version: pkg.version,
repository: pkg.repository
}
},
vite: {
Expand Down
Loading

0 comments on commit d7cac36

Please sign in to comment.