Reviews: add import key functionality

This commit is contained in:
Pieter Vander Vennet 2024-02-20 16:53:26 +01:00
parent 2482ecefc2
commit ec4f109a2e
6 changed files with 141 additions and 11 deletions

View file

@ -509,6 +509,21 @@
} }
} }
}, },
{
"id": "mangrove-key-import",
"render": {
"special": {
"type": "import_mangrove_key",
"text": {
"en": "Import a mangrove private key from backup",
"nl": "Herstel een Mangrove Private sleutel van backup"
}
},
"after": {
"en": "Uploading a private key erases your current private key. If you made reviews with it, download your current private key first"
}
}
},
{ {
"id": "translations-title", "id": "translations-title",
"label": [ "label": [

View file

@ -114,7 +114,8 @@ export default class UserRelatedState {
) )
this.mangroveIdentity = new MangroveIdentity( this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove") this.osmConnection.GetLongPreference("identity", "mangrove"),
this.osmConnection.GetPreference("identity-creation-date", "mangrove")
) )
this.preferredBackgroundLayer = this.osmConnection.GetPreference( this.preferredBackgroundLayer = this.osmConnection.GetPreference(
"preferred-background-layer", "preferred-background-layer",

View file

@ -6,11 +6,16 @@ import { GeoOperations } from "../GeoOperations"
export class MangroveIdentity { export class MangroveIdentity {
private readonly keypair: Store<CryptoKeyPair> private readonly keypair: Store<CryptoKeyPair>
private readonly mangroveIdentity: UIEventSource<string> /**
* Same as the one in the user settings
*/
public readonly mangroveIdentity: UIEventSource<string>
private readonly key_id: Store<string> private readonly key_id: Store<string>
private readonly _mangroveIdentityCreationDate: UIEventSource<string>
constructor(mangroveIdentity: UIEventSource<string>) { constructor(mangroveIdentity: UIEventSource<string>, mangroveIdentityCreationDate: UIEventSource<string>) {
this.mangroveIdentity = mangroveIdentity this.mangroveIdentity = mangroveIdentity
this._mangroveIdentityCreationDate = mangroveIdentityCreationDate
const key_id = new UIEventSource<string>(undefined) const key_id = new UIEventSource<string>(undefined)
this.key_id = key_id this.key_id = key_id
const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined) const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined)
@ -31,15 +36,16 @@ export class MangroveIdentity {
* Is written into the UIEventsource, which was passed into the constructor * Is written into the UIEventsource, which was passed into the constructor
* @constructor * @constructor
*/ */
private static async CreateIdentity(identity: UIEventSource<string>): Promise<void> { private async CreateIdentity(): Promise<void> {
const keypair = await MangroveReviews.generateKeypair() const keypair = await MangroveReviews.generateKeypair()
const jwk = await MangroveReviews.keypairToJwk(keypair) const jwk = await MangroveReviews.keypairToJwk(keypair)
if ((identity.data ?? "") !== "") { if ((this.mangroveIdentity.data ?? "") !== "") {
// Identity has been loaded via osmPreferences by now - we don't overwrite // Identity has been loaded via osmPreferences by now - we don't overwrite
return return
} }
console.log("Creating a new Mangrove identity!") console.log("Creating a new Mangrove identity!")
identity.setData(JSON.stringify(jwk)) this.mangroveIdentity.setData(JSON.stringify(jwk))
this._mangroveIdentityCreationDate.setData(new Date().toISOString())
} }
/** /**
@ -51,7 +57,7 @@ export class MangroveIdentity {
// We create the key // We create the key
try { try {
if (!Utils.runningFromConsole && (this.mangroveIdentity.data ?? "") === "") { if (!Utils.runningFromConsole && (this.mangroveIdentity.data ?? "") === "") {
await MangroveIdentity.CreateIdentity(this.mangroveIdentity) await this.CreateIdentity()
} }
} catch (e) { } catch (e) {
console.error("Could not create identity: ", e) console.error("Could not create identity: ", e)
@ -123,7 +129,7 @@ export default class FeatureReviews {
private constructor( private constructor(
feature: Feature, feature: Feature,
tagsSource: UIEventSource<Record<string, string>>, tagsSource: UIEventSource<Record<string, string>>,
mangroveIdentity?: MangroveIdentity, mangroveIdentity: MangroveIdentity,
options?: { options?: {
nameKey?: "name" | string nameKey?: "name" | string
fallbackName?: string fallbackName?: string
@ -132,8 +138,7 @@ export default class FeatureReviews {
) { ) {
const centerLonLat = GeoOperations.centerpointCoordinates(feature) const centerLonLat = GeoOperations.centerpointCoordinates(feature)
;[this._lon, this._lat] = centerLonLat ;[this._lon, this._lat] = centerLonLat
this._identity = this._identity = mangroveIdentity
mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
const nameKey = options?.nameKey ?? "name" const nameKey = options?.nameKey ?? "name"
if (feature.geometry.type === "Point") { if (feature.geometry.type === "Point") {

View file

@ -4,7 +4,7 @@
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
export let tags: UIEventSource<Record<string, any>> export let tags: UIEventSource<Record<string, any>>
export let tagKeys = tags.map((tgs) => Object.keys(tgs)) export let tagKeys = tags.map(tgs => tgs === undefined ? [] : Object.keys(tgs))
export let layer: LayerConfig | undefined = undefined export let layer: LayerConfig | undefined = undefined

View file

@ -0,0 +1,95 @@
<script lang="ts">
/**
* Allows to import a 'mangrove' private key from backup
*/
import LoginToggle from "../Base/LoginToggle.svelte"
import FileSelector from "../Base/FileSelector.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { UIEventSource } from "../../Logic/UIEventSource"
export let state: SpecialVisualizationState
export let text: string
let error: string = undefined
let success: string = undefined
function importContent(str: string) {
const parsed = JSON.parse(str)
const format = {
"crv": "P-256",
"d": undefined,
"ext": true,
"key_ops": ["sign"],
"kty": "EC",
"x": undefined,
"y": undefined,
"metadata": "Mangrove private key",
}
const neededKeys = Object.keys(format)
for (const neededKey of neededKeys) {
const expected = format[neededKey]
const actual = parsed[neededKey]
if (actual === undefined) {
error = "Not a valid key. The value for " + neededKey + " is missing"
return
}
if (typeof expected === "string" && expected !== actual) {
error = "Not a valid key. The value for " + neededKey + " should be " + expected + " but is " + actual
return
}
}
const current: UIEventSource<string> = state.userRelatedState.mangroveIdentity.mangroveIdentity
const flattened = JSON.stringify(parsed, null, "")
if (flattened === current.data) {
success = "The imported key is identical to the existing key"
return
}
console.log("Got", flattened, current)
current.setData(flattened)
success = "Applied private key"
}
async function onImport(files: FileList) {
error = undefined
success = undefined
try {
const reader = new FileReader()
reader.readAsText(files[0], "UTF-8")
// here we tell the reader what to do when it's done reading...
const content = await new Promise<string>((resolve, reject) => {
reader.onload = readerEvent => {
resolve(<string>readerEvent.target.result)
}
})
importContent(content)
} catch (e) {
error = e
}
}
</script>
<LoginToggle {state}>
<div class="flex flex-col m-1">
<FileSelector accept="application/json" multiple={false} on:submit={e => onImport(e.detail)}>
{text}
</FileSelector>
{#if error}
<div class="alert">
<Tr t={Translations.t.general.error} /> {error}</div>
{/if}
{#if success}
<div class="thanks">
{success}
</div>
{/if}
</div>
</LoginToggle>

View file

@ -92,6 +92,7 @@ import SpecialTranslation from "./Popup/TagRendering/SpecialTranslation.svelte"
import SpecialVisualisationUtils from "./SpecialVisualisationUtils" import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
import LoginButton from "./Base/LoginButton.svelte" import LoginButton from "./Base/LoginButton.svelte"
import Toggle from "./Input/Toggle" import Toggle from "./Input/Toggle"
import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte"
class NearbyImageVis implements SpecialVisualization { class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -737,6 +738,19 @@ export default class SpecialVisualizations {
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer }) return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
}, },
}, },
{
funcName: "import_mangrove_key",
docs: "Only makes sense in the usersettings. Allows to import a mangrove public key and to use this to make reviews",
args: [{
name: "text",
doc: "The text that is shown on the button",
}],
needsUrls: [],
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, argument: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
const [text] = argument
return new SvelteUIElement(ImportReviewIdentity, { state, text })
},
},
{ {
funcName: "opening_hours_table", funcName: "opening_hours_table",
docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.", docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.",