From d3d92134e03ab4d1d92ec31f09fd5aadfa9db13d Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Thu, 8 May 2025 14:20:50 +0200 Subject: [PATCH 01/24] feat: osm auth for web client --- docker-compose.yaml | 10 ++- example.env | 5 ++ firebase/functions/src/index.ts | 10 ++- firebase/functions/src/osm_auth.ts | 128 ++++++++++++++++++++++++++++- 4 files changed, 150 insertions(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index d465a704c..b1afabb16 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -230,15 +230,23 @@ services: OSM_OAUTH_API_URL: '${OSM_OAUTH_API_URL}' OSM_OAUTH_CLIENT_ID: '${OSM_OAUTH_CLIENT_ID}' OSM_OAUTH_CLIENT_SECRET: '${OSM_OAUTH_CLIENT_SECRET}' + OSM_OAUTH_REDIRECT_URI_WEB: '${OSM_OAUTH_REDIRECT_URI_WEB}' + OSM_OAUTH_APP_LOGIN_LINK_WEB: '${OSM_APP_LOGIN_LINK_WEB}' + OSM_OAUTH_CLIENT_ID_WEB: '${OSM_OAUTH_CLIENT_ID_WEB}' + OSM_OAUTH_CLIENT_SECRET_WEB: '${OSM_OAUTH_CLIENT_SECRET_WEB}' command: >- sh -c "firebase use $FIREBASE_DB && firebase target:apply hosting auth \"$FIREBASE_AUTH_SITE\" && firebase functions:config:set osm.redirect_uri=\"$OSM_OAUTH_REDIRECT_URI\" + osm.redirect_uri_web=\"$OSM_OAUTH_REDIRECT_URI_WEB\" osm.app_login_link=\"$OSM_OAUTH_APP_LOGIN_LINK\" + osm.app_login_link_web=\"$OSM_OAUTH_APP_LOGIN_LINK_WEB\" osm.api_url=\"$OSM_OAUTH_API_URL\" osm.client_id=\"$OSM_OAUTH_CLIENT_ID\" - osm.client_secret=\"$OSM_OAUTH_CLIENT_SECRET\" && + osm.client_id_web=\"$OSM_OAUTH_CLIENT_ID_WEB\" + osm.client_secret=\"$OSM_OAUTH_CLIENT_SECRET\" + osm.client_secret_web=\"$OSM_OAUTH_CLIENT_SECRET_WEB\" && firebase deploy --token $FIREBASE_TOKEN --only functions,hosting,database" django: diff --git a/example.env b/example.env index 2fa92ae33..3b5024245 100644 --- a/example.env +++ b/example.env @@ -38,10 +38,15 @@ OSMCHA_API_KEY= # OSM OAuth Configuration OSM_OAUTH_REDIRECT_URI= +OSM_OAUTH_REDIRECT_URI_WEB= OSM_OAUTH_API_URL= OSM_OAUTH_CLIENT_ID= +OSM_OAUTH_CLIENT_ID_WEB= OSM_OAUTH_CLIENT_SECRET= +OSM_OAUTH_CLIENT_SECRET_WEB= OSM_APP_LOGIN_LINK= +OSM_APP_LOGIN_LINK_WEB= + # DJANGO For more info look at django/mapswipe/settings.py::L22 DJANGO_SECRET_KEY= diff --git a/firebase/functions/src/index.ts b/firebase/functions/src/index.ts index 793ed9c71..6bb56e98c 100644 --- a/firebase/functions/src/index.ts +++ b/firebase/functions/src/index.ts @@ -8,7 +8,7 @@ admin.initializeApp(); // all functions are bundled together. It's less than ideal, but it does not // seem possible to split them using the split system for multiple sites from // https://firebase.google.com/docs/hosting/multisites -import {redirect, token} from './osm_auth'; +import {redirect, token, redirect_web, token_web} from './osm_auth'; import { formatProjectTopic, formatUserName } from './utils'; exports.osmAuth = {}; @@ -23,6 +23,14 @@ exports.osmAuth.token = functions.https.onRequest((req, res) => { token(req, res, admin); }); +exports.osmAuth.redirect_web = functions.https.onRequest((req, res) => { + redirect_web(req, res); +}); + +exports.osmAuth.token_web = functions.https.onRequest((req, res) => { + token_web(req, res, admin); +}); + /* Log the userIds of all users who finished a group to /v2/userGroups/{projectId}/{groupId}/. Gets triggered when new results of a group are written to the database. diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index d187b4e4f..5b3a84605 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -1,4 +1,4 @@ -// Firebase cloud functions to allow authentication with OpenStreet Map +// Firebase cloud functions to allow authentication with OpenStreetMap // // There are really 2 functions, which must be publicly accessible via // an https endpoint. They can be hosted on firebase under a domain like @@ -20,8 +20,10 @@ import axios from 'axios'; // will get a cryptic error about the server not being able to continue // TODO: adjust the prefix based on which deployment is done (prod/dev) const OAUTH_REDIRECT_URI = functions.config().osm?.redirect_uri; +const OAUTH_REDIRECT_URI_WEB = functions.config().osm?.redirect_web; const APP_OSM_LOGIN_DEEPLINK = functions.config().osm?.app_login_link; +const APP_OSM_LOGIN_DEEPLINK_WEB = functions.config().osm?.app_login_link_web; // the scope is taken from https://wiki.openstreetmap.org/wiki/OAuth#OAuth_2.0 // at least one seems to be required for the auth workflow to complete. @@ -51,6 +53,21 @@ function osmOAuth2Client() { return simpleOAuth2.create(credentials); } +function osmOAuth2ClientWeb() { + const credentials = { + client: { + id: functions.config().osm?.client_id_web, + secret: functions.config().osm?.client_secret_web, + }, + auth: { + tokenHost: OSM_API_URL, + tokenPath: '/oauth2/token', + authorizePath: '/oauth2/authorize', + }, + }; + return simpleOAuth2.create(credentials); +} + /** * Redirects the User to the OSM authentication consent screen. * Also the '__session' cookie is set for later state verification. @@ -84,6 +101,32 @@ export const redirect = (req: any, res: any) => { }); }; +export const redirect_web = (req: any, res: any) => { + const oauth2 = osmOAuth2ClientWeb(); + + cookieParser()(req, res, () => { + const state = + req.cookies.state || crypto.randomBytes(20).toString('hex'); + functions.logger.log('Setting verification state:', state); + // the cookie MUST be called __session for hosted functions not to + // strip it from incoming requests + // (https://firebase.google.com/docs/hosting/manage-cache#using_cookies) + res.cookie('__session', state.toString(), { + // cookie is valid for 1 hour + maxAge: 3600000, + secure: true, + httpOnly: true, + }); + const redirectUri = oauth2.authorizationCode.authorizeURL({ + redirect_uri: OAUTH_REDIRECT_URI_WEB, + scope: OAUTH_SCOPES, + state: state, + }); + functions.logger.log('Redirecting to:', redirectUri); + res.redirect(redirectUri); + }); +}; + /** * The OSM OAuth endpoing does not give us any info about the user, * so we need to get the user profile from this endpoint @@ -189,6 +232,89 @@ export const token = async (req: any, res: any, admin: any) => { } }; + +export const token_web = async (req: any, res: any, admin: any) => { + const oauth2 = osmOAuth2ClientWeb(); + + try { + return cookieParser()(req, res, async () => { + functions.logger.log( + 'Received verification state:', + req.cookies.__session, + ); + functions.logger.log('Received state:', req.query.state); + // FIXME: For security, we need to check the cookie that was set + // in the /redirect_web function on the user's browser. + // However, there seems to be a bug in firebase around this. + // https://github.com/firebase/firebase-functions/issues/544 + // and linked SO question + // firebase docs mention the need for a cookie middleware, but there + // is no info about it :( + // cross site cookies don't seem to be the issue + // WE just need to make sure the domain set on the cookies is right + if (!req.cookies.__session) { + throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.'); + } else if (req.cookies.__session !== req.query.state) { + throw new Error('State validation failed'); + } + functions.logger.log('Received auth code:', req.query.code); + let results; + + try { + // TODO: try adding auth data to request headers if + // this doesn't work + results = await oauth2.authorizationCode.getToken({ + code: req.query.code, + redirect_uri: OAUTH_REDIRECT_URI, + scope: OAUTH_SCOPES, + state: req.query.state, + }); + } catch (error: any) { + functions.logger.log('Auth token error', error, error.data.res.req); + } + // why is token called twice? + functions.logger.log( + 'Auth code exchange result received:', + results, + ); + + // We have an OSM access token and the user identity now. + const accessToken = results && results.access_token; + if (accessToken === undefined) { + throw new Error( + 'Could not get an access token from OpenStreetMap', + ); + } + // get the OSM user id and display_name + const { id, display_name } = await getOSMProfile(accessToken); + functions.logger.log('osmuser:', id, display_name); + if (id === undefined) { + // this should not happen, but help guard against creating + // invalid accounts + throw new Error('Could not obtain an account id from OSM'); + } + + // Create a Firebase account and get the Custom Auth Token. + const firebaseToken = await createFirebaseAccount( + admin, + id, + display_name, + accessToken, + ); + // build a deep link so we can send the token back to the app + // from the browser + const signinUrl = `${APP_OSM_LOGIN_DEEPLINK_WEB}?token=${firebaseToken}`; + functions.logger.log('redirecting user to', signinUrl); + res.redirect(signinUrl); + }); + } catch (error: any) { + // FIXME: this should show up in the user's browser as a bit of text + // We should figure out the various error codes available and feed them + // back into the app to allow the user to take action + return res.json({ error: error.toString() }); + } +}; + /** * Creates a Firebase account with the given user profile and returns a custom * auth token allowing the user to sign in to this account on the app. From 2885ddc3696cfefd7ed61d6ed67e2790f98550cf Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Thu, 8 May 2025 14:32:45 +0200 Subject: [PATCH 02/24] fix: use web redirect uri in token_web --- firebase/functions/src/osm_auth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 5b3a84605..80c157774 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -20,7 +20,7 @@ import axios from 'axios'; // will get a cryptic error about the server not being able to continue // TODO: adjust the prefix based on which deployment is done (prod/dev) const OAUTH_REDIRECT_URI = functions.config().osm?.redirect_uri; -const OAUTH_REDIRECT_URI_WEB = functions.config().osm?.redirect_web; +const OAUTH_REDIRECT_URI_WEB = functions.config().osm?.redirect_uri_web; const APP_OSM_LOGIN_DEEPLINK = functions.config().osm?.app_login_link; const APP_OSM_LOGIN_DEEPLINK_WEB = functions.config().osm?.app_login_link_web; @@ -265,7 +265,7 @@ export const token_web = async (req: any, res: any, admin: any) => { // this doesn't work results = await oauth2.authorizationCode.getToken({ code: req.query.code, - redirect_uri: OAUTH_REDIRECT_URI, + redirect_uri: OAUTH_REDIRECT_URI_WEB, scope: OAUTH_SCOPES, state: req.query.state, }); From f5f0c61f5490423130677df3e7280db332eb6ff9 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Thu, 8 May 2025 15:14:05 +0200 Subject: [PATCH 03/24] fix: adapt firebase.json for web app osm login --- firebase/firebase.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/firebase/firebase.json b/firebase/firebase.json index 4c56a3044..469e96e11 100644 --- a/firebase/firebase.json +++ b/firebase/firebase.json @@ -20,6 +20,14 @@ { "source": "/token", "function": "osmAuth-token" + }, + { + "source": "/redirect_web", + "function": "osmAuth-redirect_web" + }, + { + "source": "/token_web", + "function": "osmAuth-token_web" } ] }, From c25951918b20335223c2eabe39a4be4d3c4156af Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Wed, 14 May 2025 17:51:31 +0200 Subject: [PATCH 04/24] fix: osm oauth app login link env variable --- docker-compose.yaml | 2 +- example.env | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index b1afabb16..f015a71a4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -231,7 +231,7 @@ services: OSM_OAUTH_CLIENT_ID: '${OSM_OAUTH_CLIENT_ID}' OSM_OAUTH_CLIENT_SECRET: '${OSM_OAUTH_CLIENT_SECRET}' OSM_OAUTH_REDIRECT_URI_WEB: '${OSM_OAUTH_REDIRECT_URI_WEB}' - OSM_OAUTH_APP_LOGIN_LINK_WEB: '${OSM_APP_LOGIN_LINK_WEB}' + OSM_OAUTH_APP_LOGIN_LINK_WEB: '${OSM_OAUTH_APP_LOGIN_LINK_WEB}' OSM_OAUTH_CLIENT_ID_WEB: '${OSM_OAUTH_CLIENT_ID_WEB}' OSM_OAUTH_CLIENT_SECRET_WEB: '${OSM_OAUTH_CLIENT_SECRET_WEB}' command: >- diff --git a/example.env b/example.env index 3b5024245..a4ee41436 100644 --- a/example.env +++ b/example.env @@ -44,8 +44,8 @@ OSM_OAUTH_CLIENT_ID= OSM_OAUTH_CLIENT_ID_WEB= OSM_OAUTH_CLIENT_SECRET= OSM_OAUTH_CLIENT_SECRET_WEB= -OSM_APP_LOGIN_LINK= -OSM_APP_LOGIN_LINK_WEB= +OSM_OAUTH_APP_LOGIN_LINK= +OSM_OAUTH_APP_LOGIN_LINK_WEB= # DJANGO For more info look at django/mapswipe/settings.py::L22 From b006d8b51d86d19248060532d6fdea0ad1b9b1e0 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Thu, 15 May 2025 09:20:46 +0200 Subject: [PATCH 05/24] fix: avoid underscore in fb function names --- firebase/firebase.json | 8 ++++---- firebase/functions/src/index.ts | 10 +++++----- firebase/functions/src/osm_auth.ts | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/firebase/firebase.json b/firebase/firebase.json index 469e96e11..b81c02219 100644 --- a/firebase/firebase.json +++ b/firebase/firebase.json @@ -22,12 +22,12 @@ "function": "osmAuth-token" }, { - "source": "/redirect_web", - "function": "osmAuth-redirect_web" + "source": "/redirectweb", + "function": "osmAuth-redirectweb" }, { - "source": "/token_web", - "function": "osmAuth-token_web" + "source": "/tokenweb", + "function": "osmAuth-tokenweb" } ] }, diff --git a/firebase/functions/src/index.ts b/firebase/functions/src/index.ts index 6bb56e98c..c448cadc2 100644 --- a/firebase/functions/src/index.ts +++ b/firebase/functions/src/index.ts @@ -8,7 +8,7 @@ admin.initializeApp(); // all functions are bundled together. It's less than ideal, but it does not // seem possible to split them using the split system for multiple sites from // https://firebase.google.com/docs/hosting/multisites -import {redirect, token, redirect_web, token_web} from './osm_auth'; +import {redirect, token, redirectweb, tokenweb} from './osm_auth'; import { formatProjectTopic, formatUserName } from './utils'; exports.osmAuth = {}; @@ -23,12 +23,12 @@ exports.osmAuth.token = functions.https.onRequest((req, res) => { token(req, res, admin); }); -exports.osmAuth.redirect_web = functions.https.onRequest((req, res) => { - redirect_web(req, res); +exports.osmAuth.redirectweb = functions.https.onRequest((req, res) => { + redirectweb(req, res); }); -exports.osmAuth.token_web = functions.https.onRequest((req, res) => { - token_web(req, res, admin); +exports.osmAuth.tokenweb = functions.https.onRequest((req, res) => { + tokenweb(req, res, admin); }); /* diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 80c157774..9cc853a2d 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -101,7 +101,7 @@ export const redirect = (req: any, res: any) => { }); }; -export const redirect_web = (req: any, res: any) => { +export const redirectweb = (req: any, res: any) => { const oauth2 = osmOAuth2ClientWeb(); cookieParser()(req, res, () => { @@ -233,7 +233,7 @@ export const token = async (req: any, res: any, admin: any) => { }; -export const token_web = async (req: any, res: any, admin: any) => { +export const tokenweb = async (req: any, res: any, admin: any) => { const oauth2 = osmOAuth2ClientWeb(); try { @@ -244,7 +244,7 @@ export const token_web = async (req: any, res: any, admin: any) => { ); functions.logger.log('Received state:', req.query.state); // FIXME: For security, we need to check the cookie that was set - // in the /redirect_web function on the user's browser. + // in the /redirectweb function on the user's browser. // However, there seems to be a bug in firebase around this. // https://github.com/firebase/firebase-functions/issues/544 // and linked SO question From e2c22ba9b670e8442a0ebab7ea4487d7c897a153 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Thu, 15 May 2025 10:55:37 +0200 Subject: [PATCH 06/24] fix: add doc --- firebase/functions/src/osm_auth.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 9cc853a2d..b55613439 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -53,6 +53,11 @@ function osmOAuth2Client() { return simpleOAuth2.create(credentials); } +/** + * Creates a configured simple-oauth2 client for OSM for the web app. + * Configure the `osm.client_id_web` and `osm.client_secret_web` + * Google Cloud environment variables for the values below to exist + */ function osmOAuth2ClientWeb() { const credentials = { client: { From 261533105ee7a083a478b4ff0c3f7f73491dc4e5 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Mon, 19 May 2025 13:03:44 +0200 Subject: [PATCH 07/24] refactor: redirect and token --- firebase/functions/src/osm_auth.ts | 166 ++++++----------------------- 1 file changed, 34 insertions(+), 132 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index b55613439..7e721033d 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -38,7 +38,7 @@ const OSM_API_URL = functions.config().osm?.api_url; * Configure the `osm.client_id` and `osm.client_secret` * Google Cloud environment variables for the values below to exist */ -function osmOAuth2Client() { +function osmOAuth2Client(client_id, client_secret) { const credentials = { client: { id: functions.config().osm?.client_id, @@ -53,26 +53,6 @@ function osmOAuth2Client() { return simpleOAuth2.create(credentials); } -/** - * Creates a configured simple-oauth2 client for OSM for the web app. - * Configure the `osm.client_id_web` and `osm.client_secret_web` - * Google Cloud environment variables for the values below to exist - */ -function osmOAuth2ClientWeb() { - const credentials = { - client: { - id: functions.config().osm?.client_id_web, - secret: functions.config().osm?.client_secret_web, - }, - auth: { - tokenHost: OSM_API_URL, - tokenPath: '/oauth2/token', - authorizePath: '/oauth2/authorize', - }, - }; - return simpleOAuth2.create(credentials); -} - /** * Redirects the User to the OSM authentication consent screen. * Also the '__session' cookie is set for later state verification. @@ -80,8 +60,8 @@ function osmOAuth2ClientWeb() { * NOT a webview inside MapSwipe, as this would break the promise of * OAuth that we do not touch their OSM credentials */ -export const redirect = (req: any, res: any) => { - const oauth2 = osmOAuth2Client(); +function redirect2OsmOauth(redirect_uri, client_id, client_secret) { + const oauth2 = osmOAuth2Client(client_id, client_secret); cookieParser()(req, res, () => { const state = @@ -97,43 +77,31 @@ export const redirect = (req: any, res: any) => { httpOnly: true, }); const redirectUri = oauth2.authorizationCode.authorizeURL({ - redirect_uri: OAUTH_REDIRECT_URI, + redirect_uri: redirect_uri, scope: OAUTH_SCOPES, state: state, }); functions.logger.log('Redirecting to:', redirectUri); res.redirect(redirectUri); }); +} + +export const redirect = (req: any, res: any) => { + const redirect_uri = OAUTH_REDIRECT_URI; + const client_id = functions.config().osm?.client_id; + const client_secret = functions.config().osm?.client_secret; + redirect2OsmOauth(redirect_uri, client_id, client_secret); }; export const redirectweb = (req: any, res: any) => { - const oauth2 = osmOAuth2ClientWeb(); - - cookieParser()(req, res, () => { - const state = - req.cookies.state || crypto.randomBytes(20).toString('hex'); - functions.logger.log('Setting verification state:', state); - // the cookie MUST be called __session for hosted functions not to - // strip it from incoming requests - // (https://firebase.google.com/docs/hosting/manage-cache#using_cookies) - res.cookie('__session', state.toString(), { - // cookie is valid for 1 hour - maxAge: 3600000, - secure: true, - httpOnly: true, - }); - const redirectUri = oauth2.authorizationCode.authorizeURL({ - redirect_uri: OAUTH_REDIRECT_URI_WEB, - scope: OAUTH_SCOPES, - state: state, - }); - functions.logger.log('Redirecting to:', redirectUri); - res.redirect(redirectUri); - }); + const redirect_uri = OAUTH_REDIRECT_URI_WEB; + const client_id = functions.config().osm?.client_id_web; + const client_secret = functions.config().osm?.client_secret_web; + redirect2OsmOauth(redirect_uri, client_id, client_secret); }; /** - * The OSM OAuth endpoing does not give us any info about the user, + * The OSM OAuth endpoint does not give us any info about the user, * so we need to get the user profile from this endpoint */ async function getOSMProfile(accessToken: string) { @@ -155,8 +123,8 @@ async function getOSMProfile(accessToken: string) { * The Firebase custom auth token, display name, photo URL and OSM access * token are sent back to the app via a deeplink redirect. */ -export const token = async (req: any, res: any, admin: any) => { - const oauth2 = osmOAuth2Client(); +function fbToken(redirect_uri, osm_login_link, client_id, client_web) { + const oauth2 = osmOAuth2Client(client_id, client_web); try { return cookieParser()(req, res, async () => { @@ -187,7 +155,7 @@ export const token = async (req: any, res: any, admin: any) => { // this doesn't work results = await oauth2.authorizationCode.getToken({ code: req.query.code, - redirect_uri: OAUTH_REDIRECT_URI, + redirect_uri: redirect_uri, scope: OAUTH_SCOPES, state: req.query.state, }); @@ -225,7 +193,7 @@ export const token = async (req: any, res: any, admin: any) => { ); // build a deep link so we can send the token back to the app // from the browser - const signinUrl = `${APP_OSM_LOGIN_DEEPLINK}?token=${firebaseToken}`; + const signinUrl = `${osm_login_link}?token=${firebaseToken}`; functions.logger.log('redirecting user to', signinUrl); res.redirect(signinUrl); }); @@ -235,89 +203,23 @@ export const token = async (req: any, res: any, admin: any) => { // back into the app to allow the user to take action return res.json({ error: error.toString() }); } -}; - -export const tokenweb = async (req: any, res: any, admin: any) => { - const oauth2 = osmOAuth2ClientWeb(); - - try { - return cookieParser()(req, res, async () => { - functions.logger.log( - 'Received verification state:', - req.cookies.__session, - ); - functions.logger.log('Received state:', req.query.state); - // FIXME: For security, we need to check the cookie that was set - // in the /redirectweb function on the user's browser. - // However, there seems to be a bug in firebase around this. - // https://github.com/firebase/firebase-functions/issues/544 - // and linked SO question - // firebase docs mention the need for a cookie middleware, but there - // is no info about it :( - // cross site cookies don't seem to be the issue - // WE just need to make sure the domain set on the cookies is right - if (!req.cookies.__session) { - throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.'); - } else if (req.cookies.__session !== req.query.state) { - throw new Error('State validation failed'); - } - functions.logger.log('Received auth code:', req.query.code); - let results; - - try { - // TODO: try adding auth data to request headers if - // this doesn't work - results = await oauth2.authorizationCode.getToken({ - code: req.query.code, - redirect_uri: OAUTH_REDIRECT_URI_WEB, - scope: OAUTH_SCOPES, - state: req.query.state, - }); - } catch (error: any) { - functions.logger.log('Auth token error', error, error.data.res.req); - } - // why is token called twice? - functions.logger.log( - 'Auth code exchange result received:', - results, - ); +} - // We have an OSM access token and the user identity now. - const accessToken = results && results.access_token; - if (accessToken === undefined) { - throw new Error( - 'Could not get an access token from OpenStreetMap', - ); - } - // get the OSM user id and display_name - const { id, display_name } = await getOSMProfile(accessToken); - functions.logger.log('osmuser:', id, display_name); - if (id === undefined) { - // this should not happen, but help guard against creating - // invalid accounts - throw new Error('Could not obtain an account id from OSM'); - } +export const token = async (req: any, res: any, admin: any) => { + const redirect_uri = OAUTH_REDIRECT_URI; + const osm_login_link = APP_OSM_LOGIN_DEEPLINK; + const client_id = functions.config().osm?.client_id; + const client_secret = functions.config().osm?.client_secret; + fbToken(redirect_uri, osm_login_link, client_id, client_secret); +}; - // Create a Firebase account and get the Custom Auth Token. - const firebaseToken = await createFirebaseAccount( - admin, - id, - display_name, - accessToken, - ); - // build a deep link so we can send the token back to the app - // from the browser - const signinUrl = `${APP_OSM_LOGIN_DEEPLINK_WEB}?token=${firebaseToken}`; - functions.logger.log('redirecting user to', signinUrl); - res.redirect(signinUrl); - }); - } catch (error: any) { - // FIXME: this should show up in the user's browser as a bit of text - // We should figure out the various error codes available and feed them - // back into the app to allow the user to take action - return res.json({ error: error.toString() }); - } +export const tokenweb = async (req: any, res: any, admin: any) => { + const redirect_uri = OAUTH_REDIRECT_URI_WEB; + const osm_login_link = APP_OSM_LOGIN_DEEPLINK_WEB; + const client_id = functions.config().osm?.client_id_web; + const client_secret = functions.config().osm?.client_secret_web; + fbToken(redirect_uri, osm_login_link, client_id, client_secret); }; /** From 515190c54694e1d5e0373a35917d94c37f534ebf Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Mon, 19 May 2025 13:11:48 +0200 Subject: [PATCH 08/24] fix: pass req res admin --- firebase/functions/src/osm_auth.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 7e721033d..584e4df65 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -60,7 +60,7 @@ function osmOAuth2Client(client_id, client_secret) { * NOT a webview inside MapSwipe, as this would break the promise of * OAuth that we do not touch their OSM credentials */ -function redirect2OsmOauth(redirect_uri, client_id, client_secret) { +function redirect2OsmOauth(req, res, redirect_uri, client_id, client_secret) { const oauth2 = osmOAuth2Client(client_id, client_secret); cookieParser()(req, res, () => { @@ -90,14 +90,14 @@ export const redirect = (req: any, res: any) => { const redirect_uri = OAUTH_REDIRECT_URI; const client_id = functions.config().osm?.client_id; const client_secret = functions.config().osm?.client_secret; - redirect2OsmOauth(redirect_uri, client_id, client_secret); + redirect2OsmOauth(req, res, redirect_uri, client_id, client_secret); }; export const redirectweb = (req: any, res: any) => { const redirect_uri = OAUTH_REDIRECT_URI_WEB; const client_id = functions.config().osm?.client_id_web; const client_secret = functions.config().osm?.client_secret_web; - redirect2OsmOauth(redirect_uri, client_id, client_secret); + redirect2OsmOauth(req, res, redirect_uri, client_id, client_secret); }; /** @@ -123,7 +123,7 @@ async function getOSMProfile(accessToken: string) { * The Firebase custom auth token, display name, photo URL and OSM access * token are sent back to the app via a deeplink redirect. */ -function fbToken(redirect_uri, osm_login_link, client_id, client_web) { +function fbToken(req, res, admin, redirect_uri, osm_login_link, client_id, client_web) { const oauth2 = osmOAuth2Client(client_id, client_web); try { @@ -211,7 +211,7 @@ export const token = async (req: any, res: any, admin: any) => { const osm_login_link = APP_OSM_LOGIN_DEEPLINK; const client_id = functions.config().osm?.client_id; const client_secret = functions.config().osm?.client_secret; - fbToken(redirect_uri, osm_login_link, client_id, client_secret); + fbToken(req, res, admin, redirect_uri, osm_login_link, client_id, client_secret); }; export const tokenweb = async (req: any, res: any, admin: any) => { @@ -219,7 +219,7 @@ export const tokenweb = async (req: any, res: any, admin: any) => { const osm_login_link = APP_OSM_LOGIN_DEEPLINK_WEB; const client_id = functions.config().osm?.client_id_web; const client_secret = functions.config().osm?.client_secret_web; - fbToken(redirect_uri, osm_login_link, client_id, client_secret); + fbToken(req, res, admin, redirect_uri, osm_login_link, client_id, client_secret); }; /** From 52a00542a3e58ef8b649ae4d167875c8cc38f51a Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Mon, 19 May 2025 13:15:49 +0200 Subject: [PATCH 09/24] style: remove blank line padding --- firebase/functions/src/osm_auth.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 584e4df65..6fadc565f 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -203,7 +203,6 @@ function fbToken(req, res, admin, redirect_uri, osm_login_link, client_id, clien // back into the app to allow the user to take action return res.json({ error: error.toString() }); } - } export const token = async (req: any, res: any, admin: any) => { From e04f69459acbc95d35d1a5e3b071ee22fe6771b8 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Mon, 19 May 2025 13:38:24 +0200 Subject: [PATCH 10/24] fix: typing --- firebase/functions/src/osm_auth.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 6fadc565f..c344ced86 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -38,11 +38,11 @@ const OSM_API_URL = functions.config().osm?.api_url; * Configure the `osm.client_id` and `osm.client_secret` * Google Cloud environment variables for the values below to exist */ -function osmOAuth2Client(client_id, client_secret) { +function osmOAuth2Client(client_id: any, client_secret: any) { const credentials = { client: { - id: functions.config().osm?.client_id, - secret: functions.config().osm?.client_secret, + id: client_id, + secret: client_secret, }, auth: { tokenHost: OSM_API_URL, @@ -60,7 +60,7 @@ function osmOAuth2Client(client_id, client_secret) { * NOT a webview inside MapSwipe, as this would break the promise of * OAuth that we do not touch their OSM credentials */ -function redirect2OsmOauth(req, res, redirect_uri, client_id, client_secret) { +function redirect2OsmOauth(req: any, res: any, redirect_uri: string, client_id: string, client_secret: string) { const oauth2 = osmOAuth2Client(client_id, client_secret); cookieParser()(req, res, () => { @@ -123,7 +123,7 @@ async function getOSMProfile(accessToken: string) { * The Firebase custom auth token, display name, photo URL and OSM access * token are sent back to the app via a deeplink redirect. */ -function fbToken(req, res, admin, redirect_uri, osm_login_link, client_id, client_web) { +function fbToken(req: any , res: any, admin: any, redirect_uri: string, osm_login_link: string, client_id: string, client_web: string) { const oauth2 = osmOAuth2Client(client_id, client_web); try { From 3944e677682585d1f0fe35b507f7fea85bbaad90 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Mon, 19 May 2025 13:41:24 +0200 Subject: [PATCH 11/24] fix: remove space before , --- firebase/functions/src/osm_auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index c344ced86..27e4f562a 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -123,7 +123,7 @@ async function getOSMProfile(accessToken: string) { * The Firebase custom auth token, display name, photo URL and OSM access * token are sent back to the app via a deeplink redirect. */ -function fbToken(req: any , res: any, admin: any, redirect_uri: string, osm_login_link: string, client_id: string, client_web: string) { +function fbToken(req: any, res: any, admin: any, redirect_uri: string, osm_login_link: string, client_id: string, client_web: string) { const oauth2 = osmOAuth2Client(client_id, client_web); try { From 95d0c56bc6f950ebeabb7ba12c9ccb43e61bb65e Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 18:47:17 +0200 Subject: [PATCH 12/24] fix: avoid overwriting existing osm profiles on sign in --- firebase/functions/src/osm_auth.ts | 46 ++++++++++++++++++++++-------- mapswipe_workers/requirements.txt | 1 + 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 27e4f562a..18d165d6a 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -236,23 +236,19 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // with a variable length. const uid = `osm:${osmID}`; + // check if profile exists on Firebase Realtime Database + const profileExists = await admin + .database() + .ref(`v2/users/osm:${id}/`) + .get() + .then((snapshot: any) => { return snapshot.exists()}); + // Save the access token to the Firebase Realtime Database. const databaseTask = admin .database() .ref(`v2/OSMAccessToken/${uid}`) .set(accessToken); - const profileTask = admin - .database() - .ref(`v2/users/${uid}/`) - .set({ - created: new Date().toISOString(), - groupContributionCount: 0, - projectContributionCount: 0, - taskContributionCount: 0, - displayName, - }); - // Create or update the firebase user account. // This does not login the user on the app, it just ensures that a firebase // user account (linked to the OSM account) exists. @@ -272,8 +268,34 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a throw error; }); + // Only update display name if profile exists, else create profile + const profileUpdateTask = admin + .database() + .ref(`v2/users/${uid}/`) + .update({ displayName }) + + const profileCreationTask = admin + .database() + .ref(`v2/users/${uid}/`) + .set({ + created: new Date().toISOString(), + groupContributionCount: 0, + projectContributionCount: 0, + taskContributionCount: 0, + displayName, + }); + } + + const tasks = [userCreationTask, databaseTask] + + if (profileExists) { + tasks.push(profileUpdateTask) + } else { + tasks.push(profileCreationTask) + } + // Wait for all async task to complete then generate and return a custom auth token. - await Promise.all([userCreationTask, databaseTask, profileTask]); + await Promise.all(tasks); // Create a Firebase custom auth token. functions.logger.log('In createFirebaseAccount: createCustomToken'); let authToken; diff --git a/mapswipe_workers/requirements.txt b/mapswipe_workers/requirements.txt index 588754060..13462eaa5 100644 --- a/mapswipe_workers/requirements.txt +++ b/mapswipe_workers/requirements.txt @@ -5,6 +5,7 @@ firebase-admin==6.0.0 flake8==3.8.3 geojson==3.0.1 mapswipe-workers==3.0 +numpy==1.26.4 pandas==1.5.2 pre-commit==2.9.2 psycopg2-binary==2.9.3 From 29ce576e95a874c3f8caed9732f42759dcd94bb4 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 18:52:21 +0200 Subject: [PATCH 13/24] revert change to requirements --- mapswipe_workers/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/mapswipe_workers/requirements.txt b/mapswipe_workers/requirements.txt index 13462eaa5..588754060 100644 --- a/mapswipe_workers/requirements.txt +++ b/mapswipe_workers/requirements.txt @@ -5,7 +5,6 @@ firebase-admin==6.0.0 flake8==3.8.3 geojson==3.0.1 mapswipe-workers==3.0 -numpy==1.26.4 pandas==1.5.2 pre-commit==2.9.2 psycopg2-binary==2.9.3 From 57385ef430019cfdcc5ec1e56bdb5f7f8da89b76 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 19:03:18 +0200 Subject: [PATCH 14/24] fix: clean parentheses --- firebase/functions/src/osm_auth.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 18d165d6a..0c8f0dd76 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -239,7 +239,7 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // check if profile exists on Firebase Realtime Database const profileExists = await admin .database() - .ref(`v2/users/osm:${id}/`) + .ref(`v2/users/${uid}/`) .get() .then((snapshot: any) => { return snapshot.exists()}); @@ -272,7 +272,7 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a const profileUpdateTask = admin .database() .ref(`v2/users/${uid}/`) - .update({ displayName }) + .update({ displayName }); const profileCreationTask = admin .database() @@ -284,14 +284,13 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a taskContributionCount: 0, displayName, }); - } - const tasks = [userCreationTask, databaseTask] + const tasks = [userCreationTask, databaseTask]; if (profileExists) { - tasks.push(profileUpdateTask) + tasks.push(profileUpdateTask); } else { - tasks.push(profileCreationTask) + tasks.push(profileCreationTask); } // Wait for all async task to complete then generate and return a custom auth token. From a8d0ae6152e62b82060a71ce4bbb372d9f116ceb Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 19:09:10 +0200 Subject: [PATCH 15/24] fix: formatting --- firebase/functions/src/osm_auth.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 0c8f0dd76..76d820b2e 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -241,7 +241,9 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a .database() .ref(`v2/users/${uid}/`) .get() - .then((snapshot: any) => { return snapshot.exists()}); + .then((snapshot: any) => { + return snapshot.exists(); + }); // Save the access token to the Firebase Realtime Database. const databaseTask = admin @@ -270,9 +272,9 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // Only update display name if profile exists, else create profile const profileUpdateTask = admin - .database() - .ref(`v2/users/${uid}/`) - .update({ displayName }); + .database() + .ref(`v2/users/${uid}/`) + .update({ displayName }); const profileCreationTask = admin .database() From a8c662b43a8e7b931a89cb16cd5a737086f8ce9d Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 19:32:48 +0200 Subject: [PATCH 16/24] fix: use once instead of get --- firebase/functions/src/osm_auth.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 76d820b2e..c19d0bc0d 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -237,13 +237,12 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a const uid = `osm:${osmID}`; // check if profile exists on Firebase Realtime Database - const profileExists = await admin + const snapshot = await admin .database() .ref(`v2/users/${uid}/`) - .get() - .then((snapshot: any) => { - return snapshot.exists(); - }); + .once() + + const profileExists = snapshot.exists() // Save the access token to the Firebase Realtime Database. const databaseTask = admin From 0aace7f39b2ba9fc04df709193604ffd8a837765 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 19:38:01 +0200 Subject: [PATCH 17/24] reuse profileRef and add semicolons --- firebase/functions/src/osm_auth.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index c19d0bc0d..b600d0c3a 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -236,13 +236,11 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // with a variable length. const uid = `osm:${osmID}`; - // check if profile exists on Firebase Realtime Database - const snapshot = await admin - .database() - .ref(`v2/users/${uid}/`) - .once() + const profileRef = admin.database().ref(`v2/users/${uid}/`); - const profileExists = snapshot.exists() + // check if profile exists on Firebase Realtime Database + const snapshot = await profileRef.once(); + const profileExists = snapshot.exists(); // Save the access token to the Firebase Realtime Database. const databaseTask = admin @@ -270,14 +268,8 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a }); // Only update display name if profile exists, else create profile - const profileUpdateTask = admin - .database() - .ref(`v2/users/${uid}/`) - .update({ displayName }); - - const profileCreationTask = admin - .database() - .ref(`v2/users/${uid}/`) + const profileUpdateTask = profileRef.update({ displayName }); + const profileCreationTask = profileRef .set({ created: new Date().toISOString(), groupContributionCount: 0, From bbfb5ad2ce84ab2a189acdaef6da26aad12bbfd2 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 20:01:06 +0200 Subject: [PATCH 18/24] fix: add arg to once --- firebase/functions/src/osm_auth.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index b600d0c3a..3328f7079 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -236,10 +236,10 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // with a variable length. const uid = `osm:${osmID}`; - const profileRef = admin.database().ref(`v2/users/${uid}/`); + const profileRef = admin.database().ref(`v2/users/${uid}`); // check if profile exists on Firebase Realtime Database - const snapshot = await profileRef.once(); + const snapshot = await profileRef.once('value'); const profileExists = snapshot.exists(); // Save the access token to the Firebase Realtime Database. @@ -268,7 +268,7 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a }); // Only update display name if profile exists, else create profile - const profileUpdateTask = profileRef.update({ displayName }); + const profileUpdateTask = profileRef.update({ displayName: displayName }); const profileCreationTask = profileRef .set({ created: new Date().toISOString(), From 09a955df77c78a859b4f8f5be2b5a3e344b218c2 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Tue, 20 May 2025 20:13:21 +0200 Subject: [PATCH 19/24] add logging --- firebase/functions/src/osm_auth.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 3328f7079..620b247f2 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -240,6 +240,7 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // check if profile exists on Firebase Realtime Database const snapshot = await profileRef.once('value'); + functions.logger.log("Snapshot value:", snapshot.val()); const profileExists = snapshot.exists(); // Save the access token to the Firebase Realtime Database. @@ -281,8 +282,10 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a const tasks = [userCreationTask, databaseTask]; if (profileExists) { + functions.logger.log('Sign in to existing OSM profile'); tasks.push(profileUpdateTask); } else { + functions.logger.log('Sign up new OSM profile'); tasks.push(profileCreationTask); } From 61bb66ec9dfe92363c1e24636b8c3eb4c1973d41 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Wed, 21 May 2025 08:51:44 +0200 Subject: [PATCH 20/24] fix: use singlequote --- firebase/functions/src/osm_auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 620b247f2..bbc54c34e 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -240,7 +240,7 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // check if profile exists on Firebase Realtime Database const snapshot = await profileRef.once('value'); - functions.logger.log("Snapshot value:", snapshot.val()); + functions.logger.log('Snapshot value:', snapshot.val()); const profileExists = snapshot.exists(); // Save the access token to the Firebase Realtime Database. From 85710d57e32cf375ea320eb4911f6609ae1d104d Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Wed, 21 May 2025 09:01:22 +0200 Subject: [PATCH 21/24] move profile tasks definition --- firebase/functions/src/osm_auth.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index bbc54c34e..95d9169fa 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -269,23 +269,22 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a }); // Only update display name if profile exists, else create profile - const profileUpdateTask = profileRef.update({ displayName: displayName }); - const profileCreationTask = profileRef - .set({ - created: new Date().toISOString(), - groupContributionCount: 0, - projectContributionCount: 0, - taskContributionCount: 0, - displayName, - }); - const tasks = [userCreationTask, databaseTask]; if (profileExists) { functions.logger.log('Sign in to existing OSM profile'); + const profileUpdateTask = profileRef.update({ displayName: displayName }); tasks.push(profileUpdateTask); } else { functions.logger.log('Sign up new OSM profile'); + const profileCreationTask = profileRef + .set({ + created: new Date().toISOString(), + groupContributionCount: 0, + projectContributionCount: 0, + taskContributionCount: 0, + displayName, + }); tasks.push(profileCreationTask); } From 3430784817d3a94d0228928322fd617629eefe10 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Wed, 21 May 2025 09:05:14 +0200 Subject: [PATCH 22/24] fix indentation --- firebase/functions/src/osm_auth.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 95d9169fa..9f0b286f2 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -278,13 +278,13 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a } else { functions.logger.log('Sign up new OSM profile'); const profileCreationTask = profileRef - .set({ - created: new Date().toISOString(), - groupContributionCount: 0, - projectContributionCount: 0, - taskContributionCount: 0, - displayName, - }); + .set({ + created: new Date().toISOString(), + groupContributionCount: 0, + projectContributionCount: 0, + taskContributionCount: 0, + displayName, + }); tasks.push(profileCreationTask); } From ed806f6503d6008329f79fd940e09deb468d7e05 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Wed, 21 May 2025 09:33:09 +0200 Subject: [PATCH 23/24] fix: clean up --- firebase/functions/src/osm_auth.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/firebase/functions/src/osm_auth.ts b/firebase/functions/src/osm_auth.ts index 9f0b286f2..9953f2ea9 100644 --- a/firebase/functions/src/osm_auth.ts +++ b/firebase/functions/src/osm_auth.ts @@ -240,7 +240,6 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a // check if profile exists on Firebase Realtime Database const snapshot = await profileRef.once('value'); - functions.logger.log('Snapshot value:', snapshot.val()); const profileExists = snapshot.exists(); // Save the access token to the Firebase Realtime Database. @@ -268,9 +267,8 @@ async function createFirebaseAccount(admin: any, osmID: any, displayName: any, a throw error; }); - // Only update display name if profile exists, else create profile + // If profile exists, only update displayName -- else create new user profile const tasks = [userCreationTask, databaseTask]; - if (profileExists) { functions.logger.log('Sign in to existing OSM profile'); const profileUpdateTask = profileRef.update({ displayName: displayName }); From a758dc1c42707639dc394c99ee8bec1e66a29d20 Mon Sep 17 00:00:00 2001 From: Oliver Fritz Date: Wed, 21 May 2025 13:28:00 +0200 Subject: [PATCH 24/24] docs(osm-login-web): add web osm login to docs --- firebase/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/firebase/README.md b/firebase/README.md index fed47268d..c3381abc9 100644 --- a/firebase/README.md +++ b/firebase/README.md @@ -20,6 +20,11 @@ expose the authentication functions publicly. * `firebase deploy --only functions,hosting` * `firebase deploy --only database:rules` +## Deploy with Makefile +You can also deploy the changes to Firebase using make: +* Make sure to remove the firebase_deploy docker image first: `docker rmi python-mapswipe-workers-firebase_deploy` +* `make update_firebase_functions_and_db_rules` + ## Notes on OAuth (OSM login) Refer to [the notes in the app repository](https://github.com/mapswipe/mapswipe/blob/master/docs/osm_login.md). @@ -30,12 +35,16 @@ Some specifics about the related functions: - Before deploying, set the required firebase config values in environment: FIXME: replace env vars with config value names - OSM_OAUTH_REDIRECT_URI `osm.redirect_uri`: `https://dev-auth.mapswipe.org/token` or `https://auth.mapswipe.org/token` + - OSM_OAUTH_REDIRECT_URI_WEB: `https://dev-auth.mapswipe.org/tokenweb` or `https://auth.mapswipe.org/tokenweb` - OSM_OAUTH_APP_LOGIN_LINK `osm.app_login_link`: 'devmapswipe://login/osm' or 'mapswipe://login/osm' + - OSM_OAUTH_APP_LOGIN_LINK_WEB: `https://web.mapswipe.org/dev/#/osm-callback` or `https://web.mapswipe.org/#/osm-callback` - OSM_OAUTH_API_URL `osm.api_url`: 'https://master.apis.dev.openstreetmap.org/' or 'https://www.openstreetmap.org/' (include the trailing slash) - OSM_OAUTH_CLIENT_ID `osm.client_id`: find it on the OSM application page - OSM_OAUTH_CLIENT_SECRET `osm.client_secret`: same as above. Note that this can only be seen once when the application is created. Do not lose it! + - OSM_OAUTH_CLIENT_ID_WEB: This is the ID of a __different__ registered OSM OAuth client for the web version that needs to have `https://dev-auth.mapswipe.org/tokenweb` or `https://auth.mapswipe.org/tokenweb` set as redirect URI. + - OSM_OAUTH_CLIENT_SECRET_WEB: This is the secret of the OSM OAuth client for MapSwipe web version. - Deploy the functions as explained above - Expose the functions publicly through firebase hosting, this is done in `/firebase/firebase.json` under the `hosting` key.