Skip to content

Commit

Permalink
Fix page refresh on Server Login in PosititionedMap
Browse files Browse the repository at this point in the history
  • Loading branch information
r00tat committed Sep 30, 2024
1 parent c36ecec commit 2da747f
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 115 deletions.
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"moment-timezone": "^0.5.45",
"mui-color-input": "^4.0.1",
"next": "^14.2.13",
"next-auth": "^5.0.0-beta.21",
"next-auth": "^5.0.0-beta.22",
"proj4": "^2.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
128 changes: 85 additions & 43 deletions src/app/auth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import NextAuth from 'next-auth';
import NextAuth, { DefaultSession } from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import firebaseAdmin, { firestore } from '../server/firebase/admin';
import { ApiException } from './api/errors';
import { isTruthy } from '../common/boolish';
import { FirebaseUserInfo } from '../common/users';
import { USER_COLLECTION_ID } from '../components/firebase/firestore';
import firebaseAdmin, { firestore } from '../server/firebase/admin';
import { ApiException } from './api/errors';

export async function checkFirebaseToken(token: string) {
// logic to salt and hash password
Expand Down Expand Up @@ -37,6 +38,29 @@ export async function checkFirebaseToken(token: string) {
return decodedToken;
}

declare module 'next-auth' {
/**
* Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {
/**
* the users firebase id
*/
id: string;
isAuthorized: boolean;
isAdmin: boolean;
groups: string[];
/**
* By default, TypeScript merges new interface properties and overwrites existing ones.
* In this case, the default session user properties will be overwritten,
* with the new ones defined above. To keep the default session user properties,
* you need to add them back into the newly declared interface.
*/
} & DefaultSession['user'];
}
}

export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
Expand All @@ -50,10 +74,8 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
const tokenInfo = await checkFirebaseToken(credentials.firebaseToken);
// console.info(`token Info: ${JSON.stringify(tokenInfo)}`);
return {
// not displayed, using image as workaround
id: tokenInfo.sub,
// image: tokenInfo.picture,
image: tokenInfo.sub,
image: tokenInfo.picture,
email: tokenInfo.email,
name: tokenInfo.name,
};
Expand All @@ -65,36 +87,56 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
maxAge: 60 * 60,
},
// adapter: FirestoreAdapter(),
// callbacks: {
// redirect: async ({ url, baseUrl }) => {
// console.info(`redirect ${baseUrl} ${url}`);
// return '/map';
// },
// authorized: async (params) => {
// console.info(`authorized with params: ${JSON.stringify(params)}`);
// return true;
// },
// signIn: async (params) => {
// console.info(`sign in with params: ${JSON.stringify(params)}`);
// return true;
// },
// session: async ({ session, token, user }) => {
// console.info(
// `session with params: ${JSON.stringify(session)} ${JSON.stringify(
// user
// )}`
// );
// return session;
// },
// jwt: async ({ token, user, account }) => {
// console.info(
// `jwt token: ${JSON.stringify(token)}\nuser: ${JSON.stringify(
// user
// )}\n${JSON.stringify(account)}`
// );
// return token;
// },
// },
callbacks: {
// redirect: async ({ url, baseUrl }) => {
// console.info(`redirect ${baseUrl} ${url}`);
// // return '';
// // Allows relative callback URLs
// if (url.startsWith('/')) return `${baseUrl}${url}`;
// // Allows callback URLs on the same origin
// if (new URL(url).origin === baseUrl) return url;
// return baseUrl;
// },
// authorized: async (params) => {
// console.info(`authorized with params: ${JSON.stringify(params)}`);
// return true;
// },
// signIn: async (params) => {
// console.info(`sign in with params: ${JSON.stringify(params)}`);
// return true;
// },
session: async ({ session, token, user }) => {
session.user.id = token.id as string;

if (session.user.id) {
const userInfo = await firestore
.collection(USER_COLLECTION_ID)
.doc(session.user.id)
.get();
// console.info(`firebase user info: ${JSON.stringify(userInfo.data())}`);
if (userInfo.exists) {
const userData = userInfo.data() as FirebaseUserInfo;
session.user.isAuthorized = !!userData.authorized;
session.user.isAdmin = !!userData.isAdmin;
session.user.groups = ['allUsers', ...(userData.groups || [])];
}
}
console.info(
`session with params: ${JSON.stringify(session)} ${JSON.stringify(
user
)}`
);

return session;
},
jwt: ({ token, user }) => {
if (user) {
// User is available during sign-in
token.id = user.id;
}
return token;
},
},
});

export async function actionUserRequired() {
Expand All @@ -110,15 +152,15 @@ export async function actionAdminRequired() {
const session = await actionUserRequired();
// console.info(`session: ${JSON.stringify(session)}`);

if (!session.user?.image) {
if (!session.user?.id) {
throw new ApiException('User not authorized, no id set', { status: 403 });
}
const userDoc = await firestore
.collection(USER_COLLECTION_ID)
.doc(session.user?.image)
.get();
if (!userDoc.data()?.isAdmin) {
throw new ApiException('Admin require, not authorized', { status: 403 });
// const userDoc = await firestore
// .collection(USER_COLLECTION_ID)
// .doc(session.user?.id)
// .get();
if (!session.user.isAdmin) {
throw new ApiException('Admin required, not authorized', { status: 403 });
}

return session;
Expand Down
2 changes: 1 addition & 1 deletion src/app/groups/GroupAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async function getMyGroups(userId: string): Promise<Group[]> {
export async function getMyGroupsFromServer(): Promise<Group[]> {
const userInfo = await actionUserRequired();

return userInfo?.user?.image ? getMyGroups(userInfo.user?.image) : [];
return userInfo?.user?.id ? getMyGroups(userInfo.user?.id) : [];
}

export async function deleteGroupAction(groupId: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import '../styles/globals.css';
const APP_NAME = 'Einsatzkarte FFN';
const APP_DEFAULT_TITLE = 'Einsatzkarte FFN';
const APP_TITLE_TEMPLATE = '%s - PWA App';
const APP_DESCRIPTION = 'Hydraten und Einsatzkarte der FF Neusiedl am See';
const APP_DESCRIPTION = 'Hydranten und Einsatzkarte der FF Neusiedl am See';

export const metadata: Metadata = {
applicationName: APP_NAME,
Expand Down
7 changes: 6 additions & 1 deletion src/common/users.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { UserRecord } from 'firebase-admin/lib/auth/user-record';

export interface UserRecordExtended extends UserRecord {
export interface FirebaseUserInfo {
authorized?: boolean;
feuerwehr?: string;
description?: string;
messaging?: string[];
groups?: string[];
isAdmin?: boolean;
}

export interface UserRecordExtended extends UserRecord, FirebaseUserInfo {
// combine FirebaseUserInfo and UserRecord
}

export const userTextFields: { [key: string]: string } = {
Expand Down
17 changes: 1 addition & 16 deletions src/components/Map/PositionedMap.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
'use client';

import dynamic from 'next/dynamic';
import { useEffect, useState } from 'react';
import Map from './Map';
import Position from './Position';

export default function PositionedMap() {
const [hasLoaded, setHasLoaded] = useState(false);

useEffect(() => {
(async () => {
setHasLoaded(true);
})();
}, []);

if (!hasLoaded) {
return <div>Loading...</div>;
}

const Map = dynamic(() => import('./Map'));

return (
<Position>
<Map />
Expand Down
15 changes: 3 additions & 12 deletions src/components/Map/markers/PositionMarker.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { useEffect, useState } from 'react';
import { Marker, Popup, useMap } from 'react-leaflet';
'use client';

import { Marker, Popup } from 'react-leaflet';
import { usePositionContext } from '../Position';

export default function PositionMarker() {
const map = useMap();
const [initialPositionSet, setInitialPositionSet] = useState(false);
const [position, gotPosition] = usePositionContext();

useEffect(() => {
if (!initialPositionSet && gotPosition) {
console.info(`initial position, zooming to ${position}`);
setInitialPositionSet(true);
// map.setView(position);
}
}, [initialPositionSet, gotPosition, map, position]);

return (
<>
{gotPosition && (
Expand Down
17 changes: 10 additions & 7 deletions src/components/providers/AppProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import CssBaseline from '@mui/material/CssBaseline';
import { SessionProvider } from 'next-auth/react';
import React from 'react';
import About from '../../app/about/page';
import useFirebaseLogin from '../../hooks/useFirebaseLogin';
Expand Down Expand Up @@ -60,13 +61,15 @@ function AuthorizationApp({ children }: AppProps) {

export default function AppProviders({ children }: AppProps) {
return (
<FirebaseUserProvider>
<div className={styles.container}>
<CssBaseline enableColorScheme />
<SingedOutOneTapLogin />
<SessionProvider>
<FirebaseUserProvider>
<div className={styles.container}>
<CssBaseline enableColorScheme />
<SingedOutOneTapLogin />

<AuthorizationApp>{children}</AuthorizationApp>
</div>
</FirebaseUserProvider>
<AuthorizationApp>{children}</AuthorizationApp>
</div>
</FirebaseUserProvider>
</SessionProvider>
);
}
Loading

0 comments on commit 2da747f

Please sign in to comment.