Files
marco/app/services/osm-auth.js
T
raucao 4d4ed8c1ea Upgrade to oauth2-pkce 3
Bump oauth2-pkce from ^2.1.3 to ^3.0.0. In v3, getTokens() throws
ErrorAccessTokenExpired when the token has expired and no
onAccessTokenExpiry handler is configured. Add the handler to the
OSM auth client config so it automatically refreshes via the refresh
token instead of throwing
2026-08-06 16:35:11 -05:00

120 lines
3.2 KiB
JavaScript

import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { OAuth2AuthCodePkceClient } from 'oauth2-pkce';
class MarcoOsmAuthStorage {
saveState(serializedState) {
localStorage.setItem('marco:osm_auth_state', serializedState);
}
loadState() {
const state = localStorage.getItem('marco:osm_auth_state');
if (!state) return false;
try {
JSON.parse(state);
return state;
} catch (e) {
console.warn('Failed to parse OSM auth state', e);
return false;
}
}
}
export default class OsmAuthService extends Service {
@tracked isConnected = false;
@tracked userDisplayName = null;
oauthClient;
constructor() {
super(...arguments);
const clientId =
import.meta.env.VITE_OSM_CLIENT_ID || 'YOUR_CLIENT_ID_HERE';
const oauthUrl =
import.meta.env.VITE_OSM_OAUTH_URL || 'https://www.openstreetmap.org';
const redirectUrl =
import.meta.env.VITE_OSM_REDIRECT_URI ||
`${window.location.origin}/oauth/osm/callback`;
this.oauthClient = new OAuth2AuthCodePkceClient(
{
scopes: ['read_prefs', 'write_api'],
authorizationUrl: `${oauthUrl}/oauth2/authorize`,
tokenUrl: `${oauthUrl}/oauth2/token`,
clientId: clientId,
redirectUrl: redirectUrl,
storeRefreshToken: true,
onAccessTokenExpiry() {
return this.exchangeRefreshTokenForAccessToken();
},
},
new MarcoOsmAuthStorage()
);
this.restoreSession();
}
async restoreSession() {
try {
await this.oauthClient.ready;
} catch (e) {
console.warn('oauthClient.ready failed', e);
}
const isAuthorized = await this.oauthClient.isAuthorized();
if (isAuthorized) {
this.isConnected = true;
const storedName = localStorage.getItem('marco:osm_user_display_name');
if (storedName) {
this.userDisplayName = storedName;
} else {
await this.fetchUserInfo();
}
}
}
async login() {
await this.oauthClient.requestAuthorizationCode();
}
async handleCallback() {
await this.oauthClient.receiveCode();
await this.oauthClient.getTokens();
this.isConnected = true;
await this.fetchUserInfo();
}
async logout() {
await this.oauthClient.reset();
this.isConnected = false;
this.userDisplayName = null;
localStorage.removeItem('marco:osm_user_display_name');
}
async fetchUserInfo() {
try {
const tokens = await this.oauthClient.getTokens();
// eslint-disable-next-line warp-drive/no-external-request-patterns
const response = await fetch(
'https://api.openstreetmap.org/api/0.6/user/details.json',
{
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
}
);
if (response.ok) {
const data = await response.json();
console.debug('OSM data:', data);
const displayName = data.user.display_name;
this.userDisplayName = displayName;
localStorage.setItem('marco:osm_user_display_name', displayName);
} else {
console.error('Failed to fetch OSM user info', response.status);
}
} catch (e) {
console.error('Error fetching OSM user info', e);
}
}
}