From 51f08c19a1ae1f7cde06307eb93b6a2be9df0ecc Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet <pietervdvn@posteo.net>
Date: Fri, 1 Sep 2023 21:36:39 +0200
Subject: [PATCH] Chore: update to OAuth 2.0, fix #1548

---
 land.html                      |  5 +-
 package-lock.json              | 71 ++++------------------------
 package.json                   |  4 +-
 src/Logic/Osm/OsmConnection.ts | 85 +++++++++++++++++++---------------
 src/land.ts                    | 12 +++++
 5 files changed, 73 insertions(+), 104 deletions(-)
 create mode 100644 src/land.ts

diff --git a/land.html b/land.html
index cd155ea56..a9fd46b34 100644
--- a/land.html
+++ b/land.html
@@ -2,9 +2,6 @@
 <html>
 <head><title>MapComplete Auth</title></head>
 <body>
-<script>
-    opener.authComplete(window.location.href);
-    window.close();
-</script>
+<script type="module" src="./src/land.ts"></script>
 </body>
 </html>
diff --git a/package-lock.json b/package-lock.json
index 8b49afc9a..9d1829c33 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "mapcomplete",
-  "version": "0.31.2",
+  "version": "0.31.4",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "mapcomplete",
-      "version": "0.31.2",
+      "version": "0.31.4",
       "license": "GPL-3.0-or-later",
       "dependencies": {
         "@rgossiaux/svelte-headlessui": "^1.0.2",
@@ -40,7 +40,7 @@
         "mangrove-reviews-typescript": "^1.1.0",
         "maplibre-gl": "^3.2.0",
         "opening_hours": "^3.6.0",
-        "osm-auth": "^1.0.2",
+        "osm-auth": "^2.2.0",
         "osmtogeojson": "^3.0.0-beta.5",
         "papaparse": "^5.3.1",
         "pic4carto": "^2.1.15",
@@ -7300,17 +7300,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/jshashes": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/jshashes/-/jshashes-1.0.8.tgz",
-      "integrity": "sha512-btmQZ/w1rj8Lb6nEwvhjM7nBYoj54yaEFo2PWh3RkxZ8qNwuvOxvQYN/JxVuwoMmdIluL+XwYVJ+pEEZoSYybQ==",
-      "bin": {
-        "hashes": "bin/hashes"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/json-schema": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
@@ -8189,18 +8178,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/ohauth": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/ohauth/-/ohauth-1.0.1.tgz",
-      "integrity": "sha512-R9ZUN3+FVCwzeOOHCJpzA9jw/byRxp5O9X06mTL6Sp/LIQn/rLrMv6cwYctX+hoIKzRUsalGJXZ1kG5wBmSskQ==",
-      "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
-      "dependencies": {
-        "jshashes": "~1.0.8"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -8372,16 +8349,14 @@
       }
     },
     "node_modules/osm-auth": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/osm-auth/-/osm-auth-1.1.2.tgz",
-      "integrity": "sha512-oLaU+c/TP7eKAZpBN4S1mv/N94IXp5A+wLpDfAVlpq/b6iikas8ZthXPqhM8QKg/qB8RaKvZPJgxqYS+5m8G8g==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/osm-auth/-/osm-auth-2.2.0.tgz",
+      "integrity": "sha512-x93jAMaYWqPgfVeOMydFLFpFC8ERnlIKXwiUOrYYWTDEWqq15K/BI5UAjzuYXvLg0WxVxM8YC4N1T30SZeKJBQ==",
       "dependencies": {
-        "ohauth": "~1.0.1",
-        "resolve-url": "~0.2.1",
         "store": "~2.0.12"
       },
       "engines": {
-        "node": ">=14"
+        "node": ">=16"
       }
     },
     "node_modules/osm-polygon-features": {
@@ -9191,12 +9166,6 @@
         "protocol-buffers-schema": "^3.3.1"
       }
     },
-    "node_modules/resolve-url": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
-      "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==",
-      "deprecated": "https://github.com/lydell/resolve-url#deprecated"
-    },
     "node_modules/restore-cursor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -17830,11 +17799,6 @@
       "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
       "dev": true
     },
-    "jshashes": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/jshashes/-/jshashes-1.0.8.tgz",
-      "integrity": "sha512-btmQZ/w1rj8Lb6nEwvhjM7nBYoj54yaEFo2PWh3RkxZ8qNwuvOxvQYN/JxVuwoMmdIluL+XwYVJ+pEEZoSYybQ=="
-    },
     "json-schema": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
@@ -18513,14 +18477,6 @@
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
       "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
     },
-    "ohauth": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/ohauth/-/ohauth-1.0.1.tgz",
-      "integrity": "sha512-R9ZUN3+FVCwzeOOHCJpzA9jw/byRxp5O9X06mTL6Sp/LIQn/rLrMv6cwYctX+hoIKzRUsalGJXZ1kG5wBmSskQ==",
-      "requires": {
-        "jshashes": "~1.0.8"
-      }
-    },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -18651,12 +18607,10 @@
       "dev": true
     },
     "osm-auth": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/osm-auth/-/osm-auth-1.1.2.tgz",
-      "integrity": "sha512-oLaU+c/TP7eKAZpBN4S1mv/N94IXp5A+wLpDfAVlpq/b6iikas8ZthXPqhM8QKg/qB8RaKvZPJgxqYS+5m8G8g==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/osm-auth/-/osm-auth-2.2.0.tgz",
+      "integrity": "sha512-x93jAMaYWqPgfVeOMydFLFpFC8ERnlIKXwiUOrYYWTDEWqq15K/BI5UAjzuYXvLg0WxVxM8YC4N1T30SZeKJBQ==",
       "requires": {
-        "ohauth": "~1.0.1",
-        "resolve-url": "~0.2.1",
         "store": "~2.0.12"
       }
     },
@@ -19236,11 +19190,6 @@
         "protocol-buffers-schema": "^3.3.1"
       }
     },
-    "resolve-url": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
-      "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg=="
-    },
     "restore-cursor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
diff --git a/package.json b/package.json
index 20ce152ef..bdbeb8cb2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mapcomplete",
-  "version": "0.31.4",
+  "version": "0.32.0",
   "repository": "https://github.com/pietervdvn/MapComplete",
   "description": "A small website to edit OSM easily",
   "bugs": "https://github.com/pietervdvn/MapComplete/issues",
@@ -93,7 +93,7 @@
     "mangrove-reviews-typescript": "^1.1.0",
     "maplibre-gl": "^3.2.0",
     "opening_hours": "^3.6.0",
-    "osm-auth": "^1.0.2",
+    "osm-auth": "^2.2.0",
     "osmtogeojson": "^3.0.0-beta.5",
     "papaparse": "^5.3.1",
     "pic4carto": "^2.1.15",
diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts
index a14d30849..42c6a9304 100644
--- a/src/Logic/Osm/OsmConnection.ts
+++ b/src/Logic/Osm/OsmConnection.ts
@@ -1,7 +1,9 @@
-import osmAuth from "osm-auth"
-import { Store, Stores, UIEventSource } from "../UIEventSource"
-import { OsmPreferences } from "./OsmPreferences"
-import { Utils } from "../../Utils"
+// @ts-ignore
+import {osmAuth} from "osm-auth"
+import {Store, Stores, UIEventSource} from "../UIEventSource"
+import {OsmPreferences} from "./OsmPreferences"
+import {Utils} from "../../Utils"
+import {LocalStorageSource} from "../Web/LocalStorageSource";
 
 export default class UserDetails {
     public loggedIn = false
@@ -22,22 +24,26 @@ export default class UserDetails {
     }
 }
 
+export interface AuthConfig {
+    oauth_client_id: string
+    oauth_secret: string
+    url: string
+}
+
 export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
 
 export class OsmConnection {
-    public static readonly oauth_configs = {
+    public static readonly oauth_configs: Record<string, AuthConfig> = {
         osm: {
-            oauth_consumer_key: "hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem",
-            oauth_secret: "wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI",
+            oauth_client_id: 'sa1ngLJBJ8McmzHElN8NYtIDm5TZTYEYhq3-0snO4Qc',
+            oauth_secret: 'XU_cD5Mvw9VKk9T0t_gO8V7cbRC4Hmw2Tb4Rv0Zmz-U',
             url: "https://www.openstreetmap.org",
-            // OAUTH 1.0 application
-            // https://www.openstreetmap.org/user/Pieter%20Vander%20Vennet/oauth_clients/7404
         },
         "osm-test": {
-            oauth_consumer_key: "Zgr7EoKb93uwPv2EOFkIlf3n9NLwj5wbyfjZMhz2",
-            oauth_secret: "3am1i1sykHDMZ66SGq4wI2Z7cJMKgzneCHp3nctn",
-            url: "https://master.apis.dev.openstreetmap.org",
-        },
+            oauth_client_id: "HwUn6GPxGm1m9WwMarxTglhy6dBTM4YkaV1I9h6pDGU"
+             oauth_secret: "luFZtPJg7j96K6WM6RpcZ_3M-r6muuDq6fG1ygk0I_4",
+             url: "https://master.apis.dev.openstreetmap.org",
+         }
     }
     public auth
     public userDetails: UIEventSource<UserDetails>
@@ -53,11 +59,7 @@ export class OsmConnection {
         "not-attempted"
     )
     public preferencesHandler: OsmPreferences
-    public readonly _oauth_config: {
-        oauth_consumer_key: string
-        oauth_secret: string
-        url: string
-    }
+    public readonly _oauth_config: AuthConfig
     private readonly _dryRun: Store<boolean>
     private fakeUser: boolean
     private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []
@@ -190,6 +192,7 @@ export class OsmConnection {
         const self = this
         console.log("Trying to log in...")
         this.updateAuthObject()
+        LocalStorageSource.Get("location_before_login").setData(window.location.href)
         this.auth.xhr(
             {
                 method: "GET",
@@ -202,13 +205,8 @@ export class OsmConnection {
                     if (err.status == 401) {
                         console.log("Clearing tokens...")
                         // Not authorized - our token probably got revoked
-                        // Reset all the tokens
-                        const tokens = [
-                            "https://www.openstreetmap.orgoauth_request_token_secret",
-                            "https://www.openstreetmap.orgoauth_token",
-                            "https://www.openstreetmap.orgoauth_token_secret",
-                        ]
-                        tokens.forEach((token) => localStorage.removeItem(token))
+                        self.auth.logout();
+                        self.LogOut()
                     }
                     return
                 }
@@ -252,7 +250,7 @@ export class OsmConnection {
                 if (homeEl !== undefined && homeEl[0] !== undefined) {
                     const lat = parseFloat(homeEl[0].getAttribute("lat"))
                     const lon = parseFloat(homeEl[0].getAttribute("lon"))
-                    data.home = { lat: lat, lon: lon }
+                    data.home = {lat: lat, lon: lon}
                 }
 
                 self.loadingStatus.setData("logged-in")
@@ -310,6 +308,7 @@ export class OsmConnection {
     ): Promise<any> {
         return await this.interact(path, "POST", header, content)
     }
+
     public async put(
         path: string,
         content?: string,
@@ -355,13 +354,13 @@ export class OsmConnection {
             console.warn("Dryrun enabled - not actually opening note with text ", text)
             return new Promise<{ id: number }>((ok) => {
                 window.setTimeout(
-                    () => ok({ id: Math.floor(Math.random() * 1000) }),
+                    () => ok({id: Math.floor(Math.random() * 1000)}),
                     Math.random() * 5000
                 )
             })
         }
         const auth = this.auth
-        const content = { lat, lon, text }
+        const content = {lat, lon, text}
         const response = await this.post("notes.json", JSON.stringify(content), {
             "Content-Type": "application/json",
         })
@@ -389,7 +388,7 @@ export class OsmConnection {
             console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
             return new Promise<{ id: number }>((ok, error) => {
                 window.setTimeout(
-                    () => ok({ id: Math.floor(Math.random() * 1000) }),
+                    () => ok({id: Math.floor(Math.random() * 1000)}),
                     Math.random() * 5000
                 )
             })
@@ -430,7 +429,7 @@ export class OsmConnection {
         })
         const parsed = JSON.parse(response)
         console.log("Uploaded GPX track", parsed)
-        return { id: parsed }
+        return {id: parsed}
     }
 
     public addCommentToNote(id: number | string, text: string): Promise<void> {
@@ -486,15 +485,27 @@ export class OsmConnection {
         // Same for an iframe...
 
         this.auth = new osmAuth({
-            oauth_consumer_key: this._oauth_config.oauth_consumer_key,
-            oauth_secret: this._oauth_config.oauth_secret,
+            client_id: this._oauth_config.oauth_client_id,
             url: this._oauth_config.url,
-            landing: standalone ? undefined : window.location.href,
+            scope: "read_prefs write_prefs write_api write_gpx write_notes",
+            redirect_uri: window.location.protocol + "//" + window.location.host + "/land.html",
             singlepage: !standalone,
             auto: true,
         })
     }
 
+    /**
+     * To be called by land.html
+     */
+    public finishLogin(callback: ((previousURL: string) => void)) {
+        this.auth.authenticate(function() {
+            // Fully authed at this point
+            console.log("Authentication successful!")
+            const previousLocation = LocalStorageSource.Get("location_before_login")
+            callback(previousLocation.data)
+        });
+    }
+
     private CheckForMessagesContinuously() {
         const self = this
         if (this.isChecking) {
@@ -511,7 +522,7 @@ export class OsmConnection {
 
     private UpdateCapabilities(): void {
         const self = this
-        this.FetchCapabilities().then(({ api, gpx }) => {
+        this.FetchCapabilities().then(({api, gpx}) => {
             self.apiIsOnline.setData(api)
             self.gpxServiceIsOnline.setData(gpx)
         })
@@ -519,18 +530,18 @@ export class OsmConnection {
 
     private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> {
         if (Utils.runningFromConsole) {
-            return { api: "online", gpx: "online" }
+            return {api: "online", gpx: "online"}
         }
         const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities")
         if (result["content"] === undefined) {
             console.log("Something went wrong:", result)
-            return { api: "unreachable", gpx: "unreachable" }
+            return {api: "unreachable", gpx: "unreachable"}
         }
         const xmlRaw = result["content"]
         const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml")
         const statusEl = parsed.getElementsByTagName("status")[0]
         const api = <OsmServiceState>statusEl.getAttribute("api")
         const gpx = <OsmServiceState>statusEl.getAttribute("gpx")
-        return { api, gpx }
+        return {api, gpx}
     }
 }
diff --git a/src/land.ts b/src/land.ts
new file mode 100644
index 000000000..18ceedea4
--- /dev/null
+++ b/src/land.ts
@@ -0,0 +1,12 @@
+import {OsmConnection} from "./Logic/Osm/OsmConnection";
+
+console.log("Authorizing...");
+new OsmConnection().finishLogin(previousURL => {
+    const fallback = window.location.protocol+"//"+window.location.host+"/index.html"
+    previousURL ??= fallback
+    if(previousURL.indexOf("/land") > 0){
+        previousURL = fallback
+    }
+    console.log("Redirecting to", previousURL)
+    window.location.href = previousURL
+})