forked from MapComplete/MapComplete
UX: allow to share login tokens via QR-code for educational context
This commit is contained in:
parent
93c613aa89
commit
a90387c4f3
6 changed files with 123 additions and 48 deletions
|
@ -1531,6 +1531,48 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "share-login-title",
|
||||
"render": {
|
||||
"en": "<h3>Login via QR code</h3>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "share-login-explanation",
|
||||
"render": {
|
||||
"en": "With the below QR-code, you can login on another device without having to share your password"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "share-login-group",
|
||||
"render": {
|
||||
"special": {
|
||||
"type": "group",
|
||||
"header": "share-login-group-title",
|
||||
"labels": "share-login-qr"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "share-login-group-title",
|
||||
"labels": [
|
||||
"hidden"
|
||||
],
|
||||
"render": {
|
||||
"en": "Allow to log in and act as <b>{_name}</b>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "share-login-qr",
|
||||
"labels": [
|
||||
"hidden"
|
||||
],
|
||||
"render": {
|
||||
"special": {
|
||||
"type": "qr_login"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "debug-title",
|
||||
"render": {
|
||||
|
|
|
@ -154,7 +154,8 @@ export class OsmConnection {
|
|||
constructor(options?: {
|
||||
dryRun?: Store<boolean>
|
||||
fakeUser?: false | boolean
|
||||
oauth_token?: UIEventSource<string>
|
||||
oauth_token?: UIEventSource<string>,
|
||||
shared_cookie?: string,
|
||||
// Used to keep multiple changesets open and to write to the correct changeset
|
||||
singlePage?: boolean
|
||||
attemptLogin?: boolean
|
||||
|
@ -205,6 +206,10 @@ export class OsmConnection {
|
|||
|
||||
this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false)
|
||||
|
||||
if (options?.shared_cookie) {
|
||||
this.setToken(options?.shared_cookie)
|
||||
}
|
||||
|
||||
this.updateAuthObject(false)
|
||||
AndroidPolyfill.inAndroid.addCallback(() => {
|
||||
this.updateAuthObject(false)
|
||||
|
@ -600,6 +605,9 @@ export class OsmConnection {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the login token. Sharing this will allow to mimic the user session on another device
|
||||
*/
|
||||
public getToken(): string {
|
||||
// https://www.openstreetmap.orgoauth2_access_token
|
||||
let prefix = this.Backend()
|
||||
|
@ -608,12 +616,20 @@ export class OsmConnection {
|
|||
}
|
||||
return (
|
||||
QueryParameters.GetQueryParameter(prefix + "oauth_token", undefined).data ??
|
||||
window.localStorage.getItem(this._oauth_config.url + "oauth2_access_token")
|
||||
window.localStorage.getItem(this.getLoginCookieName())
|
||||
)
|
||||
}
|
||||
|
||||
public setToken(token: string) {
|
||||
window.localStorage.setItem(this.getLoginCookieName(), token)
|
||||
}
|
||||
|
||||
private getLoginCookieName() {
|
||||
return this._oauth_config.url + "oauth2_access_token"
|
||||
}
|
||||
|
||||
private async loginAndroidPolyfill() {
|
||||
const key = "https://www.openstreetmap.orgoauth2_access_token"
|
||||
const key = this.getLoginCookieName()
|
||||
if (localStorage.getItem(key)) {
|
||||
// We are probably already logged in
|
||||
return
|
||||
|
@ -629,6 +645,7 @@ export class OsmConnection {
|
|||
}
|
||||
await this.loadUserInfo()
|
||||
}
|
||||
|
||||
private updateAuthObject(autoLogin: boolean) {
|
||||
let redirect_uri = Utils.runningFromConsole
|
||||
? "https://mapcomplete.org/land.html"
|
||||
|
|
|
@ -1,42 +1,14 @@
|
|||
import { Utils } from "../../Utils"
|
||||
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
||||
export class ThemeMetaTagging {
|
||||
public static readonly themeName = "usersettings"
|
||||
public static readonly themeName = "usersettings"
|
||||
|
||||
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
|
||||
feat.properties._description
|
||||
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
|
||||
?.at(1)
|
||||
)
|
||||
Utils.AddLazyProperty(
|
||||
feat.properties,
|
||||
"_d",
|
||||
() => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? ""
|
||||
)
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
|
||||
((feat) => {
|
||||
const e = document.createElement("div")
|
||||
e.innerHTML = feat.properties._d
|
||||
return Array.from(e.getElementsByTagName("a")).filter(
|
||||
(a) => a.href.match(/mastodon|en.osm.town/) !== null
|
||||
)[0]?.href
|
||||
})(feat)
|
||||
)
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
|
||||
((feat) => {
|
||||
const e = document.createElement("div")
|
||||
e.innerHTML = feat.properties._d
|
||||
return Array.from(e.getElementsByTagName("a")).filter(
|
||||
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
|
||||
)[0]?.href
|
||||
})(feat)
|
||||
)
|
||||
Utils.AddLazyProperty(
|
||||
feat.properties,
|
||||
"_mastodon_candidate",
|
||||
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
|
||||
)
|
||||
feat.properties["__current_backgroun"] = "initial_value"
|
||||
}
|
||||
}
|
||||
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
|
||||
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
|
||||
feat.properties['__current_backgroun'] = 'initial_value'
|
||||
}
|
||||
}
|
|
@ -37,9 +37,11 @@ export class WithUserRelatedState {
|
|||
}
|
||||
this.theme = theme
|
||||
this.featureSwitches = new FeatureSwitchState(theme)
|
||||
|
||||
this.osmConnection = new OsmConnection({
|
||||
dryRun: this.featureSwitches.featureSwitchIsTesting,
|
||||
fakeUser: this.featureSwitches.featureSwitchFakeUser.data,
|
||||
shared_cookie: QueryParameters.GetQueryParameter("shared_oauth_cookie", undefined, "Used to share a session with another device - this saves logging in at another device").data,
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
|
|
|
@ -13,16 +13,38 @@
|
|||
export let state: SpecialVisualizationState
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let feature: Feature
|
||||
|
||||
let [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
export let extraUrlParams: Record<string, string> = {}
|
||||
|
||||
const includeLayout = window.location.pathname.split("/").at(-1).startsWith("theme")
|
||||
const layout = includeLayout ? "layout=" + state.theme.id + "&" : ""
|
||||
let id: Store<string> = tags.mapD((tags) => tags.id)
|
||||
let url = id.mapD(
|
||||
extraUrlParams["z"] ??= 15
|
||||
if (includeLayout) {
|
||||
extraUrlParams["layout"] ??= state.theme.id
|
||||
}
|
||||
if (feature) {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
extraUrlParams["lon"] ??= "" + lon
|
||||
extraUrlParams["lat"] ??= "" + lat
|
||||
} else if (state?.mapProperties?.location?.data) {
|
||||
const l = state?.mapProperties?.location?.data
|
||||
extraUrlParams["lon"] ??= "" + l.lon
|
||||
extraUrlParams["lat"] ??= "" + l.lat
|
||||
}
|
||||
|
||||
const params = []
|
||||
for (const key in extraUrlParams) {
|
||||
console.log(key, "-->", extraUrlParams[key])
|
||||
params.push(key + "=" + encodeURIComponent(extraUrlParams[key]))
|
||||
}
|
||||
let url = id.map((id) => {
|
||||
if (id) {
|
||||
return "#" + id
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}).map(
|
||||
(id) =>
|
||||
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
|
||||
`#${id}`
|
||||
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${params.join("&")}${id}`
|
||||
)
|
||||
|
||||
function toggleSize() {
|
||||
|
@ -32,9 +54,11 @@
|
|||
size.setData(smallSize)
|
||||
}
|
||||
}
|
||||
|
||||
url.addCallbackAndRunD(url => console.log("URL IS", url))
|
||||
</script>
|
||||
|
||||
{#if $id.startsWith("node/-")}
|
||||
{#if $id?.startsWith("node/-")}
|
||||
<!-- Not yet uploaded, doesn't have a fixed ID -->
|
||||
<Loading />
|
||||
{:else}
|
||||
|
|
|
@ -14,6 +14,9 @@ import LanguageUtils from "../../Utils/LanguageUtils"
|
|||
import LanguagePicker from "../InputElement/LanguagePicker.svelte"
|
||||
import PendingChangesIndicator from "../BigComponents/PendingChangesIndicator.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import QrCode from "../Popup/QrCode.svelte"
|
||||
|
||||
export class SettingsVisualisations {
|
||||
public static initList(): SpecialVisualizationSvelte[] {
|
||||
|
@ -146,6 +149,21 @@ export class SettingsVisualisations {
|
|||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "qr_login",
|
||||
args: [],
|
||||
docs: "A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out",
|
||||
group: "settings",
|
||||
constr(state: SpecialVisualizationState, tags: UIEventSource<Record<string, string>>, argument: string[], feature: Feature, layer: LayerConfig): SvelteUIElement {
|
||||
const shared_oauth_cookie = state.osmConnection.getToken()
|
||||
return new SvelteUIElement(QrCode, {
|
||||
state,
|
||||
tags,
|
||||
feature,
|
||||
extraUrlParams: { shared_oauth_cookie }
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
funcName: "logout",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue