Skip to content

Commit 6148a99

Browse files
authored
chore: make application reject older wallets (#424)
1 parent 5e1caaf commit 6148a99

File tree

19 files changed

+454
-63
lines changed

19 files changed

+454
-63
lines changed

infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export function createScanLogic({
262262
session: get(session),
263263
w3id: w3idResult,
264264
signature: signature,
265+
appVersion: "0.4.0",
265266
};
266267

267268
console.log("🔐 Auth payload with signature:", {

platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { Request, Response } from "express";
22
import { v4 as uuidv4 } from "uuid";
33
import { EventEmitter } from "events";
44
import { auth } from "firebase-admin";
5+
import { isVersionValid } from "../utils/version";
6+
7+
const MIN_REQUIRED_VERSION = "0.4.0";
8+
59
export class AuthController {
610
private eventEmitter: EventEmitter;
711

@@ -51,13 +55,32 @@ export class AuthController {
5155

5256
login = async (req: Request, res: Response) => {
5357
try {
54-
const { ename, session } = req.body;
58+
const { ename, session, appVersion } = req.body;
5559

5660
console.log(req.body)
5761

5862
if (!ename) {
5963
return res.status(400).json({ error: "ename is required" });
6064
}
65+
66+
if (!session) {
67+
return res.status(400).json({ error: "session is required" });
68+
}
69+
70+
// Check app version - missing version is treated as old version
71+
if (!appVersion || !isVersionValid(appVersion, MIN_REQUIRED_VERSION)) {
72+
const errorMessage = {
73+
error: true,
74+
message: `Your eID Wallet app version is outdated. Please update to version ${MIN_REQUIRED_VERSION} or later.`,
75+
type: "version_mismatch"
76+
};
77+
this.eventEmitter.emit(session, errorMessage);
78+
return res.status(400).json({
79+
error: "App version too old",
80+
message: errorMessage.message
81+
});
82+
}
83+
6184
const token = await auth().createCustomToken(ename);
6285
console.log(token);
6386

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Compares two semantic version strings
3+
* @param version1 - First version string (e.g., "0.4.0")
4+
* @param version2 - Second version string (e.g., "0.3.0")
5+
* @returns -1 if version1 < version2, 0 if equal, 1 if version1 > version2
6+
*/
7+
export function compareVersions(version1: string, version2: string): number {
8+
const v1Parts = version1.split('.').map(Number);
9+
const v2Parts = version2.split('.').map(Number);
10+
11+
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
12+
const v1Part = v1Parts[i] || 0;
13+
const v2Part = v2Parts[i] || 0;
14+
15+
if (v1Part < v2Part) return -1;
16+
if (v1Part > v2Part) return 1;
17+
}
18+
19+
return 0;
20+
}
21+
22+
/**
23+
* Checks if the app version meets the minimum required version
24+
* @param appVersion - The version from the app (e.g., "0.4.0")
25+
* @param minVersion - The minimum required version (e.g., "0.4.0")
26+
* @returns true if appVersion >= minVersion, false otherwise
27+
*/
28+
export function isVersionValid(appVersion: string, minVersion: string): boolean {
29+
return compareVersions(appVersion, minVersion) >= 0;
30+
}
31+
32+

platforms/blabsy/src/components/common/maintenance-banner.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export function MaintenanceBanner(): JSX.Element | null {
1818
const registryUrl =
1919
process.env.NEXT_PUBLIC_REGISTRY_URL ||
2020
'http://localhost:4321';
21-
const response = await axios.get<Motd>(`${registryUrl}/motd`);
21+
const response = await axios.get<Motd>(`${registryUrl}/motd`, {
22+
timeout: 5000 // 5 second timeout
23+
});
2224
setMotd(response.data);
2325

2426
// Check if this message has been dismissed
@@ -27,6 +29,19 @@ export function MaintenanceBanner(): JSX.Element | null {
2729
setIsDismissed(dismissed === response.data.message);
2830
}
2931
} catch (error) {
32+
// Silently handle network errors - registry service may not be available
33+
// Only log non-network errors for debugging
34+
if (axios.isAxiosError(error)) {
35+
if (
36+
error.code === 'ECONNABORTED' ||
37+
error.code === 'ERR_NETWORK' ||
38+
error.message === 'Network Error'
39+
) {
40+
// Network error - registry service unavailable, silently fail
41+
return;
42+
}
43+
}
44+
// Log other errors (like 404, 500, etc.) for debugging
3045
console.error('Failed to fetch motd:', error);
3146
}
3247
};

platforms/blabsy/src/components/login/login-main.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { isMobileDevice, getDeepLinkUrl } from '@lib/utils/mobile-detection';
99
export function LoginMain(): JSX.Element {
1010
const { signInWithCustomToken } = useAuth();
1111
const [qr, setQr] = useState<string>();
12+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
1213

1314
function watchEventStream(id: string): void {
1415
const sseUrl = new URL(
@@ -19,13 +20,37 @@ export function LoginMain(): JSX.Element {
1920

2021
eventSource.onopen = (): void => {
2122
console.log('Successfully connected.');
23+
setErrorMessage(null);
2224
};
2325

2426
eventSource.onmessage = async (e): Promise<void> => {
25-
const data = JSON.parse(e.data as string) as { token: string };
26-
const { token } = data;
27-
console.log(token);
28-
await signInWithCustomToken(token);
27+
const data = JSON.parse(e.data as string) as {
28+
token?: string;
29+
error?: boolean;
30+
message?: string;
31+
type?: string;
32+
};
33+
34+
// Check for error messages (version mismatch)
35+
if (data.error && data.type === 'version_mismatch') {
36+
setErrorMessage(
37+
data.message ||
38+
'Your eID Wallet app version is outdated. Please update to continue.'
39+
);
40+
eventSource.close();
41+
return;
42+
}
43+
44+
// Handle successful authentication
45+
if (data.token) {
46+
console.log(data.token);
47+
await signInWithCustomToken(data.token);
48+
}
49+
};
50+
51+
eventSource.onerror = (): void => {
52+
console.error('SSE connection error');
53+
eventSource.close();
2954
};
3055
}
3156
const getOfferData = async (): Promise<void> => {
@@ -89,6 +114,14 @@ export function LoginMain(): JSX.Element {
89114
Join Blabsy today.
90115
</h2>
91116
<div>
117+
{errorMessage && (
118+
<div className='mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg'>
119+
<p className='font-semibold'>
120+
Authentication Error
121+
</p>
122+
<p className='text-sm'>{errorMessage}</p>
123+
</div>
124+
)}
92125
{isMobileDevice() ? (
93126
<div className='flex flex-col gap-4 items-center'>
94127
<div className='text-xs text-gray-500 text-center max-w-xs'>

platforms/dreamSync/client/src/components/auth/login-screen.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function LoginScreen() {
1111
const [sessionId, setSessionId] = useState<string>("");
1212
const [isConnecting, setIsConnecting] = useState(false);
1313
const [isLoading, setIsLoading] = useState(true);
14+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
1415

1516
useEffect(() => {
1617
const getAuthOffer = async () => {
@@ -45,6 +46,15 @@ export function LoginScreen() {
4546
eventSource.onmessage = (event) => {
4647
try {
4748
const data = JSON.parse(event.data);
49+
50+
// Check for error messages (version mismatch)
51+
if (data.error && data.type === 'version_mismatch') {
52+
setErrorMessage(data.message || 'Your eID Wallet app version is outdated. Please update to continue.');
53+
eventSource.close();
54+
return;
55+
}
56+
57+
// Handle successful authentication
4858
if (data.user && data.token) {
4959
setIsConnecting(true);
5060
// Store the token and user ID directly
@@ -108,6 +118,13 @@ export function LoginScreen() {
108118
</p>
109119
</div>
110120

121+
{errorMessage && (
122+
<div className="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg">
123+
<p className="font-semibold">Authentication Error</p>
124+
<p className="text-sm">{errorMessage}</p>
125+
</div>
126+
)}
127+
111128
{qrCode && (
112129
<div className="flex justify-center mb-6">
113130
{isMobileDevice() ? (

platforms/dreamsync-api/src/controllers/AuthController.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { v4 as uuidv4 } from "uuid";
33
import { UserService } from "../services/UserService";
44
import { EventEmitter } from "events";
55
import { signToken } from "../utils/jwt";
6+
import { isVersionValid } from "../utils/version";
7+
8+
const MIN_REQUIRED_VERSION = "0.4.0";
69

710
export class AuthController {
811
private userService: UserService;
@@ -53,7 +56,7 @@ export class AuthController {
5356

5457
login = async (req: Request, res: Response) => {
5558
try {
56-
const { ename, session, w3id, signature } = req.body;
59+
const { ename, session, w3id, signature, appVersion } = req.body;
5760

5861
if (!ename) {
5962
return res.status(400).json({ error: "ename is required" });
@@ -63,6 +66,20 @@ export class AuthController {
6366
return res.status(400).json({ error: "session is required" });
6467
}
6568

69+
// Check app version - missing version is treated as old version
70+
if (!appVersion || !isVersionValid(appVersion, MIN_REQUIRED_VERSION)) {
71+
const errorMessage = {
72+
error: true,
73+
message: `Your eID Wallet app version is outdated. Please update to version ${MIN_REQUIRED_VERSION} or later.`,
74+
type: "version_mismatch"
75+
};
76+
this.eventEmitter.emit(session, errorMessage);
77+
return res.status(400).json({
78+
error: "App version too old",
79+
message: errorMessage.message
80+
});
81+
}
82+
6683
// Find user by ename (handles @ symbol variations)
6784
const user = await this.userService.findByEname(ename);
6885

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Compares two semantic version strings
3+
* @param version1 - First version string (e.g., "0.4.0")
4+
* @param version2 - Second version string (e.g., "0.3.0")
5+
* @returns -1 if version1 < version2, 0 if equal, 1 if version1 > version2
6+
*/
7+
export function compareVersions(version1: string, version2: string): number {
8+
const v1Parts = version1.split('.').map(Number);
9+
const v2Parts = version2.split('.').map(Number);
10+
11+
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
12+
const v1Part = v1Parts[i] || 0;
13+
const v2Part = v2Parts[i] || 0;
14+
15+
if (v1Part < v2Part) return -1;
16+
if (v1Part > v2Part) return 1;
17+
}
18+
19+
return 0;
20+
}
21+
22+
/**
23+
* Checks if the app version meets the minimum required version
24+
* @param appVersion - The version from the app (e.g., "0.4.0")
25+
* @param minVersion - The minimum required version (e.g., "0.4.0")
26+
* @returns true if appVersion >= minVersion, false otherwise
27+
*/
28+
export function isVersionValid(appVersion: string, minVersion: string): boolean {
29+
return compareVersions(appVersion, minVersion) >= 0;
30+
}
31+
32+

platforms/eVoting/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"lucide-react": "^0.453.0",
4747
"next": "15.4.2",
4848
"next-qrcode": "^2.5.1",
49+
"next.js": "^1.0.3",
4950
"qrcode.react": "^4.2.0",
5051
"react": "19.1.0",
5152
"react-day-picker": "^9.11.1",

platforms/eVoting/src/components/auth/login-screen.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function LoginScreen() {
1010
const [qrCode, setQrCode] = useState<string>("");
1111
const [sessionId, setSessionId] = useState<string>("");
1212
const [isConnecting, setIsConnecting] = useState(false);
13+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
1314

1415
useEffect(() => {
1516
const getAuthOffer = async () => {
@@ -35,6 +36,15 @@ export function LoginScreen() {
3536
eventSource.onmessage = (event) => {
3637
try {
3738
const data = JSON.parse(event.data);
39+
40+
// Check for error messages (version mismatch)
41+
if (data.error && data.type === 'version_mismatch') {
42+
setErrorMessage(data.message || 'Your eID Wallet app version is outdated. Please update to continue.');
43+
eventSource.close();
44+
return;
45+
}
46+
47+
// Handle successful authentication
3848
if (data.user && data.token) {
3949
setIsConnecting(true);
4050
// Store the token and user ID directly
@@ -71,6 +81,12 @@ export function LoginScreen() {
7181
</div>
7282
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
7383
<div className="flex flex-col items-center space-y-6">
84+
{errorMessage && (
85+
<div className="w-full mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg">
86+
<p className="font-semibold">Authentication Error</p>
87+
<p className="text-sm">{errorMessage}</p>
88+
</div>
89+
)}
7490
{qrCode ? (
7591
<div className="p-4 bg-white border-2 border-gray-200 rounded-lg">
7692
<QRCodeSVG

0 commit comments

Comments
 (0)