diff --git a/pages/api/token-login.ts b/pages/api/token-login.ts
new file mode 100644
index 00000000..ba97a0c5
--- /dev/null
+++ b/pages/api/token-login.ts
@@ -0,0 +1,15 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { serverFetchOsmUser } from '../../src/services/osmApiAuthServer';
+
+// TODO upgrade Nextjs and use export async function POST(request: NextRequest) {
+export default async (req: NextApiRequest, res: NextApiResponse) => {
+ try {
+ const { osmAccessToken } = req.cookies;
+ const user = await serverFetchOsmUser({ osmAccessToken });
+
+ res.status(200).json({ user });
+ } catch (err) {
+ console.error(err); // eslint-disable-line no-console
+ res.status(err.code ?? 400).send(String(err));
+ }
+};
diff --git a/public/oauth-token.html b/public/oauth-token.html
index d21113ce..b181e4b1 100644
--- a/public/oauth-token.html
+++ b/public/oauth-token.html
@@ -9,12 +9,12 @@
OsmAPP auth popup
[en] If this popup doesn't close automatically, you may close it and
- proceed back to osmapp.org.
+ proceed back to the app.
[cs] Pokud se toto okno nezavře automaticky, můžete ho zavřít a
- pokračovat na osmapp.org.
+ pokračovat do aplikace.
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 60382754..380f34d7 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -94,12 +94,12 @@ const IndexWithProviders = () => {
);
};
-const App = ({ featureFromRouter, initialMapView, hpCookie }) => {
+const App = ({ featureFromRouter, initialMapView, cookies }) => {
const mapView = getMapViewFromHash() || initialMapView;
return (
-
+
-
+
@@ -113,10 +113,10 @@ const App = ({ featureFromRouter, initialMapView, hpCookie }) => {
App.getInitialProps = async (ctx) => {
await setIntlForSSR(ctx); // needed for lang urls like /es/node/123
- const { hideHomepage: hpCookie } = nextCookies(ctx);
+ const cookies = nextCookies(ctx);
const featureFromRouter = await getInititalFeature(ctx);
const initialMapView = await getInitialMapView(ctx);
- return { featureFromRouter, initialMapView, hpCookie };
+ return { featureFromRouter, initialMapView, cookies };
};
export default App;
diff --git a/src/components/FeaturePanel/ObjectsAround.tsx b/src/components/FeaturePanel/ObjectsAround.tsx
index 3e2ce21d..1eb5a96e 100644
--- a/src/components/FeaturePanel/ObjectsAround.tsx
+++ b/src/components/FeaturePanel/ObjectsAround.tsx
@@ -4,10 +4,9 @@ import Router from 'next/router';
import { fetchAroundFeature } from '../../services/osmApi';
import { useFeatureContext } from '../utils/FeatureContext';
import { Feature } from '../../services/types';
-import { getOsmappLink, getUrlOsmId } from '../../services/helpers';
+import { FetchError, getOsmappLink, getUrlOsmId } from '../../services/helpers';
import Maki from '../utils/Maki';
import { t } from '../../services/intl';
-import { FetchError } from '../../services/fetch';
import { trimText, useMobileMode } from '../helpers';
import { getLabel } from '../../helpers/featureLabel';
import { useUserThemeContext } from '../../helpers/theme';
diff --git a/src/components/Map/TopMenu/HamburgerMenu.tsx b/src/components/Map/TopMenu/HamburgerMenu.tsx
index 31c24c8a..f07a4b27 100644
--- a/src/components/Map/TopMenu/HamburgerMenu.tsx
+++ b/src/components/Map/TopMenu/HamburgerMenu.tsx
@@ -156,10 +156,20 @@ const ThemeSelection = () => {
const UserLogin = forwardRef(({ closeMenu }, ref) => {
const { osmUser, handleLogin, handleLogout } = useOsmAuthContext();
+ const login = () => {
+ closeMenu();
+ handleLogin();
+ };
+ const logout = () => {
+ closeMenu();
+ setTimeout(() => {
+ handleLogout();
+ }, 100);
+ };
if (!osmUser) {
return (
-
-
- {t('user.logout')}
+ {t('user.logout')}
>
);
});
-// TODO maybe
-//
-//
-//
-//
-
// TODO custom Item components are not keyboard accesible
// seems like a bug in material-ui
// https://github.com/mui-org/material-ui/issues/22912
diff --git a/src/components/Map/TopMenu/LoginIcon.tsx b/src/components/Map/TopMenu/LoginIcon.tsx
index 7cc03126..d3e88b06 100644
--- a/src/components/Map/TopMenu/LoginIcon.tsx
+++ b/src/components/Map/TopMenu/LoginIcon.tsx
@@ -8,6 +8,7 @@ const StyledUserImg = styled.img`
width: 24px;
height: 24px;
border-radius: 50%;
+ background-color: white;
`;
export const LoginIcon = ({ onClick }) => {
const { osmUser, userImage } = useOsmAuthContext();
diff --git a/src/components/utils/FeatureContext.tsx b/src/components/utils/FeatureContext.tsx
index 57c9e842..6d388ad8 100644
--- a/src/components/utils/FeatureContext.tsx
+++ b/src/components/utils/FeatureContext.tsx
@@ -29,13 +29,13 @@ export const FeatureContext = createContext(undefined);
interface Props {
featureFromRouter: Feature | null;
children: ReactNode;
- hpCookie: string;
+ cookies: Record;
}
export const FeatureProvider = ({
children,
featureFromRouter,
- hpCookie,
+ cookies,
}: Props) => {
const [preview, setPreview] = useState(null);
const [feature, setFeature] = useState(featureFromRouter);
@@ -49,7 +49,7 @@ export const FeatureProvider = ({
}, [featureFromRouter]);
const [homepageShown, showHomepage, hideHomepage] = useBoolState(
- feature == null && hpCookie !== 'yes',
+ feature == null && cookies.hideHomepage !== 'yes',
);
const persistShowHomepage = () => {
setFeature(null);
diff --git a/src/components/utils/OsmAuthContext.tsx b/src/components/utils/OsmAuthContext.tsx
index 945f6a3d..3c2dc338 100644
--- a/src/components/utils/OsmAuthContext.tsx
+++ b/src/components/utils/OsmAuthContext.tsx
@@ -1,7 +1,6 @@
import React, { createContext, useContext, useState } from 'react';
import {
- fetchOsmUser,
- getOsmUser,
+ loginAndfetchOsmUser,
osmLogout,
OsmUser,
} from '../../services/osmApiAuth';
@@ -15,11 +14,16 @@ interface OsmAuthType {
handleLogout: () => void;
}
+const useOsmUserState = (cookies) => {
+ const initialState = cookies.osmUserForSSR;
+ return useState(initialState);
+};
+
export const OsmAuthContext = createContext(undefined);
-export const OsmAuthProvider = ({ children }) => {
+export const OsmAuthProvider = ({ children, cookies }) => {
const mapStateContext = useMapStateContext();
- const [osmUser, setOsmUser] = useState(getOsmUser());
+ const [osmUser, setOsmUser] = useOsmUserState(cookies);
const successfulLogin = (user: OsmUser) => {
setOsmUser(user);
@@ -29,7 +33,7 @@ export const OsmAuthProvider = ({ children }) => {
});
};
- const handleLogin = () => fetchOsmUser().then(successfulLogin);
+ const handleLogin = () => loginAndfetchOsmUser().then(successfulLogin);
const handleLogout = () => osmLogout().then(() => setOsmUser(undefined));
const value = {
diff --git a/src/services/fetch.ts b/src/services/fetch.ts
index 62a95b59..cd1014d6 100644
--- a/src/services/fetch.ts
+++ b/src/services/fetch.ts
@@ -1,21 +1,7 @@
import fetch from 'isomorphic-unfetch';
import { getCache, getKey, writeCacheSafe } from './fetchCache';
import { isBrowser } from '../components/helpers';
-
-export class FetchError extends Error {
- constructor(
- public message: string = '',
- public code: string,
- public data: string,
- ) {
- super();
- }
-
- toString() {
- const suffix = this.data && ` Data: ${this.data.substring(0, 1000)}`;
- return `Fetch: ${this.message}${suffix}`;
- }
-}
+import { FetchError } from './helpers';
// TODO cancel request in map.on('click', ...)
const abortableQueues: Record = {};
diff --git a/src/services/helpers.ts b/src/services/helpers.ts
index 399518e0..47a25b8f 100644
--- a/src/services/helpers.ts
+++ b/src/services/helpers.ts
@@ -154,3 +154,18 @@ export const doShortenerRedirect = (ctx) => {
return false;
};
+
+export class FetchError extends Error {
+ constructor(
+ public message: string = '',
+ public code: string,
+ public data: string,
+ ) {
+ super();
+ }
+
+ toString() {
+ const suffix = this.data && ` Data: ${this.data.substring(0, 1000)}`;
+ return `Fetch: ${this.message}${suffix}`;
+ }
+}
diff --git a/src/services/osmApi.ts b/src/services/osmApi.ts
index ef30ee0e..9da8bac2 100644
--- a/src/services/osmApi.ts
+++ b/src/services/osmApi.ts
@@ -1,5 +1,12 @@
-import { getApiId, getShortId, getUrlOsmId, OsmApiId, prod } from './helpers';
-import { FetchError, fetchJson } from './fetch';
+import {
+ FetchError,
+ getApiId,
+ getShortId,
+ getUrlOsmId,
+ OsmApiId,
+ prod,
+} from './helpers';
+import { fetchJson } from './fetch';
import { Feature, Position } from './types';
import { removeFetchCache } from './fetchCache';
import { overpassAroundToSkeletons } from './overpassAroundToSkeletons';
diff --git a/src/services/osmApiAuth.ts b/src/services/osmApiAuth.ts
index a4f74822..ba5f2ea3 100644
--- a/src/services/osmApiAuth.ts
+++ b/src/services/osmApiAuth.ts
@@ -1,3 +1,4 @@
+import Cookies from 'js-cookie';
import escape from 'lodash/escape';
import getConfig from 'next/config';
import { osmAuth } from 'osm-auth';
@@ -57,25 +58,32 @@ export const fetchOsmUser = async (): Promise => {
path: '/api/0.6/user/details.json',
});
const details = JSON.parse(response).user;
- const user = {
+ return {
name: details.display_name,
imageUrl:
details.img?.href ??
`https://www.gravatar.com/avatar/${details.id}?s=24&d=robohash`,
};
-
- window.localStorage.setItem('osm_user', JSON.stringify(user));
- return user;
};
-export const getOsmUser = (): OsmUser | undefined =>
- auth.authenticated()
- ? JSON.parse(window.localStorage.getItem('osm_user'))
- : undefined;
+export const loginAndfetchOsmUser = async (): Promise => {
+ const osmUser = await fetchOsmUser();
+
+ const { url } = auth.options();
+ const osmAccessToken = localStorage.getItem(`${url}oauth2_access_token`);
+ const osmUserForSSR = JSON.stringify(osmUser);
+ Cookies.set('osmAccessToken', osmAccessToken, { path: '/', expires: 365 });
+ Cookies.set('osmUserForSSR', osmUserForSSR, { path: '/', expires: 365 });
+
+ await fetch('/api/token-login');
+
+ return osmUser;
+};
export const osmLogout = async () => {
auth.logout();
- window.localStorage.removeItem('osm_user');
+ Cookies.remove('osmAccessToken', { path: '/' });
+ Cookies.remove('osmUserForSSR', { path: '/' });
};
const getChangesetXml = ({ changesetComment, feature }) => {
diff --git a/src/services/osmApiAuthServer.ts b/src/services/osmApiAuthServer.ts
new file mode 100644
index 00000000..6454e30c
--- /dev/null
+++ b/src/services/osmApiAuthServer.ts
@@ -0,0 +1,47 @@
+import fetch from 'isomorphic-unfetch';
+import { FetchError } from './helpers';
+
+interface OsmAuthFetchOpts extends RequestInit {
+ osmAccessToken: string;
+}
+
+const osmAuthFetch = async (
+ endpoint: string,
+ options: OsmAuthFetchOpts,
+): Promise => {
+ const { osmAccessToken, ...restOptions } = options;
+ if (!osmAccessToken) throw new Error('No access token');
+
+ const url = `https://api.openstreetmap.org${endpoint}`;
+ const headers = {
+ 'User-Agent': 'osmapp (SSR; https://osmapp.org/)',
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ Authorization: `Bearer ${osmAccessToken}`,
+ };
+
+ const response = await fetch(url, {
+ ...restOptions,
+ headers,
+ });
+
+ if (!response.ok || response.status < 200 || response.status >= 300) {
+ const data = await response.text();
+ throw new FetchError(
+ `${response.status} ${response.statusText}`,
+ `${response.status}`,
+ data,
+ );
+ }
+
+ return response.json();
+};
+
+export const serverFetchOsmUser = async (
+ options: OsmAuthFetchOpts,
+): Promise<{ id: number; username: string }> => {
+ const { user } = await osmAuthFetch('/api/0.6/user/details.json', options);
+ return {
+ id: user.id,
+ username: user.display_name,
+ };
+};