forked from MapComplete/MapComplete
		
	Reviews: add import key functionality
This commit is contained in:
		
							parent
							
								
									2482ecefc2
								
							
						
					
					
						commit
						ec4f109a2e
					
				
					 6 changed files with 141 additions and 11 deletions
				
			
		| 
						 | 
				
			
			@ -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",
 | 
			
		||||
      "label": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,7 +114,8 @@ export default class UserRelatedState {
 | 
			
		|||
        )
 | 
			
		||||
 | 
			
		||||
        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(
 | 
			
		||||
            "preferred-background-layer",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,11 +6,16 @@ import { GeoOperations } from "../GeoOperations"
 | 
			
		|||
 | 
			
		||||
export class MangroveIdentity {
 | 
			
		||||
    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 _mangroveIdentityCreationDate: UIEventSource<string>
 | 
			
		||||
 | 
			
		||||
    constructor(mangroveIdentity: UIEventSource<string>) {
 | 
			
		||||
    constructor(mangroveIdentity: UIEventSource<string>, mangroveIdentityCreationDate: UIEventSource<string>) {
 | 
			
		||||
        this.mangroveIdentity = mangroveIdentity
 | 
			
		||||
        this._mangroveIdentityCreationDate = mangroveIdentityCreationDate
 | 
			
		||||
        const key_id = new UIEventSource<string>(undefined)
 | 
			
		||||
        this.key_id = key_id
 | 
			
		||||
        const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined)
 | 
			
		||||
| 
						 | 
				
			
			@ -31,15 +36,16 @@ export class MangroveIdentity {
 | 
			
		|||
     * Is written into the UIEventsource, which was passed into the constructor
 | 
			
		||||
     * @constructor
 | 
			
		||||
     */
 | 
			
		||||
    private static async CreateIdentity(identity: UIEventSource<string>): Promise<void> {
 | 
			
		||||
    private async CreateIdentity(): Promise<void> {
 | 
			
		||||
        const keypair = await MangroveReviews.generateKeypair()
 | 
			
		||||
        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
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        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
 | 
			
		||||
            try {
 | 
			
		||||
                if (!Utils.runningFromConsole && (this.mangroveIdentity.data ?? "") === "") {
 | 
			
		||||
                    await MangroveIdentity.CreateIdentity(this.mangroveIdentity)
 | 
			
		||||
                    await this.CreateIdentity()
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error("Could not create identity: ", e)
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +129,7 @@ export default class FeatureReviews {
 | 
			
		|||
    private constructor(
 | 
			
		||||
        feature: Feature,
 | 
			
		||||
        tagsSource: UIEventSource<Record<string, string>>,
 | 
			
		||||
        mangroveIdentity?: MangroveIdentity,
 | 
			
		||||
        mangroveIdentity: MangroveIdentity,
 | 
			
		||||
        options?: {
 | 
			
		||||
            nameKey?: "name" | string
 | 
			
		||||
            fallbackName?: string
 | 
			
		||||
| 
						 | 
				
			
			@ -132,8 +138,7 @@ export default class FeatureReviews {
 | 
			
		|||
    ) {
 | 
			
		||||
        const centerLonLat = GeoOperations.centerpointCoordinates(feature)
 | 
			
		||||
        ;[this._lon, this._lat] = centerLonLat
 | 
			
		||||
        this._identity =
 | 
			
		||||
            mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
 | 
			
		||||
        this._identity = mangroveIdentity
 | 
			
		||||
        const nameKey = options?.nameKey ?? "name"
 | 
			
		||||
 | 
			
		||||
        if (feature.geometry.type === "Point") {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
  import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										95
									
								
								src/UI/Reviews/ImportReviewIdentity.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/UI/Reviews/ImportReviewIdentity.svelte
									
										
									
									
									
										Normal 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>
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +92,7 @@ import SpecialTranslation from "./Popup/TagRendering/SpecialTranslation.svelte"
 | 
			
		|||
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
 | 
			
		||||
import LoginButton from "./Base/LoginButton.svelte"
 | 
			
		||||
import Toggle from "./Input/Toggle"
 | 
			
		||||
import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte"
 | 
			
		||||
 | 
			
		||||
class NearbyImageVis implements SpecialVisualization {
 | 
			
		||||
    // 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 })
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                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",
 | 
			
		||||
                docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue