fixed push notifications and auth modified
CI/CD / Backend Unit Tests (push) Successful in 2m17s
CI/CD / Deploy (push) Successful in 2m26s

This commit is contained in:
2026-06-11 13:30:36 +02:00
parent 3afe5e2931
commit 7d7e641f5b
8 changed files with 72 additions and 9 deletions
+4
View File
@@ -87,3 +87,7 @@ frontend/android/
# Expo
frontend/expo-env.d.ts
CLAUDE.md
+3
View File
@@ -86,3 +86,6 @@ This project is currently under semi-active development as a learning and experi
## Notes
This project is just me messing around with stuff while making an app i will use and perhaps others will find it useful
need to made .creds .env and init directory with subjects.txt
@@ -3,6 +3,7 @@ package dev.ksan.etfoglasiserver.config;
import dev.ksan.etfoglasiserver.service.JWTService;
import dev.ksan.etfoglasiserver.service.MyUserDetailsService;
import dev.ksan.etfoglasiserver.service.UserService;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -36,7 +37,12 @@ public class JwtFilter extends OncePerRequestFilter {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
try {
username = jwtService.extractEmail(token);
} catch (JwtException e) {
// expired, malformed, or otherwise invalid token - treat as unauthenticated
username = null;
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
@@ -1,17 +1,13 @@
package dev.ksan.etfoglasiserver.service;
import dev.ksan.etfoglasiserver.model.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.function.Function;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
@@ -29,7 +25,7 @@ public class JWTService {
return Jwts.builder().claims().add(claims).subject(email)
.issuedAt(new Date(System.currentTimeMillis())).expiration(new Date(System.currentTimeMillis()+60*60*300))
.issuedAt(new Date(System.currentTimeMillis())).expiration(new Date(System.currentTimeMillis()+1000L*60*60*24*60))
.and().signWith(getKey()).compact();
}
+12 -2
View File
@@ -7,11 +7,20 @@ import {
onMessage,
onNotificationOpenedApp,
getInitialNotification,
setBackgroundMessageHandler,
} from '@react-native-firebase/messaging';
import { registerDeviceToken, listenForTokenRefresh } from '@/services/notifications';
import { registerDeviceToken, listenForTokenRefresh, displayLocalNotification } from '@/services/notifications';
import { AuthProvider, useAuth } from "@/context/AuthContext";
import Toast from 'react-native-toast-message';
// Registered at module scope so it's installed as soon as this entry file
// loads, which is required for it to fire while the app is backgrounded/killed.
setBackgroundMessageHandler(getMessaging(), async (remoteMessage) => {
const title = remoteMessage.data?.title as string | undefined;
const body = remoteMessage.data?.body as string | undefined;
await displayLocalNotification(title, body);
});
function NotificationSetup() {
const { user, loading } = useAuth();
@@ -27,7 +36,8 @@ function NotificationSetup() {
console.log('Foreground notification:', msg);
Toast.show({
type: 'info',
text1: msg.notification?.title ?? 'New Notification',
text1: (msg.data?.title as string | undefined) ?? 'New Notification',
text2: msg.data?.body as string | undefined,
position: 'top',
visibilityTime: 4000,
});
+16 -1
View File
@@ -1,9 +1,10 @@
import React, { createContext, useContext, useEffect, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import Toast from "react-native-toast-message";
import {
authApi,
LoginPayload, RegisterPayload, subscriptionsApi, userApi, UserUpdateDTO,
LoginPayload, RegisterPayload, setUnauthorizedHandler, subscriptionsApi, userApi, UserUpdateDTO,
} from "@/services/api";
type User = {
@@ -45,6 +46,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setUser(u);
};
useEffect(() => {
setUnauthorizedHandler(() => {
persist(null);
Toast.show({
type: 'info',
text1: 'Session expired',
text2: 'Please sign in again.',
position: 'top',
visibilityTime: 4000,
});
});
return () => setUnauthorizedHandler(null);
}, []);
const updateNotificationType = async (type: 'PUSH_NOTIFICATION' | 'NO_NOTIFICATION') => {
if (!user) return;
await userApi.updateNotificationType(type, user.token);
+9
View File
@@ -6,6 +6,14 @@ function authHeader(token?: string) {
return token ? { Authorization: `Bearer ${token}` } : {};
}
let onUnauthorized: (() => void) | null = null;
// Called once from AuthProvider so api.tsx can trigger a logout/relogin
// when the backend rejects a request due to a missing/expired/invalid token.
export function setUnauthorizedHandler(handler: (() => void) | null) {
onUnauthorized = handler;
}
async function request<T>(
method: string,
path: string,
@@ -30,6 +38,7 @@ async function request<T>(
console.log('Failed URL:', res.url);
console.log('Status:', res.status);
console.log('Response:', errorText);
if (res.status === 401 && token) onUnauthorized?.();
throw new Error(errorText);
}
return res.json();
+20
View File
@@ -5,6 +5,7 @@ import {
onTokenRefresh,
AuthorizationStatus,
} from '@react-native-firebase/messaging';
import notifee, { AndroidImportance } from '@notifee/react-native';
import { post } from '@/services/api';
export async function registerDeviceToken(authToken: string): Promise<void> {
@@ -33,3 +34,22 @@ export function listenForTokenRefresh(authToken: string): () => void {
await post('/device-tokens/register', { token: newToken }, authToken);
});
}
export async function displayLocalNotification(title?: string, body?: string) {
if (!title && !body) return;
const channelId = await notifee.createChannel({
id: 'default',
name: 'General',
importance: AndroidImportance.HIGH,
});
await notifee.displayNotification({
title,
body,
android: {
channelId,
pressAction: { id: 'default' },
},
});
}