Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(login): generate clientID & persist across sessions #526

Merged
merged 4 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,8 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-html-to-pdf (0.12.0):
- React-Core
- react-native-menu (1.1.6):
Expand Down Expand Up @@ -1798,7 +1800,7 @@ PODS:
- React-Core
- RNCClipboard (1.14.3):
- React-Core
- RNDeviceInfo (13.0.0):
- RNDeviceInfo (13.2.0):
- React-Core
- RNFastImage (8.6.3):
- React-Core
Expand Down Expand Up @@ -2056,6 +2058,7 @@ DEPENDENCIES:
- react-native-date-picker (from `../node_modules/react-native-date-picker`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)"
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- react-native-html-to-pdf (from `../node_modules/react-native-html-to-pdf`)
- "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
Expand Down Expand Up @@ -2220,6 +2223,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker"
react-native-geolocation:
:path: "../node_modules/@react-native-community/geolocation"
react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values"
react-native-html-to-pdf:
:path: "../node_modules/react-native-html-to-pdf"
react-native-menu:
Expand Down Expand Up @@ -2389,6 +2394,7 @@ SPEC CHECKSUMS:
react-native-date-picker: 06a4d96ab525a163c7a90bccd68833d136b0bb13
react-native-document-picker: 7343222102ece8aec51390717f47ad7119c7921f
react-native-geolocation: 0c7a8496962d3268ac5b28f22a8e5d7a04c43f1a
react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
react-native-html-to-pdf: 4c5c6e26819fe202971061594058877aa9b25265
react-native-menu: b7e42b26d3014c993db0f0ae3d5fcc523d5585bd
react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac
Expand Down Expand Up @@ -2427,7 +2433,7 @@ SPEC CHECKSUMS:
ReactCommon: 422e364463f33e336fc4db196aeb50fd801d90d6
RNCAsyncStorage: d35c79ffba52c1013013e16b1fc295aec2feabb6
RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb
RNDeviceInfo: 55264dd7cc939dad6e9c231a7621311f5277f1dc
RNDeviceInfo: 29e01d5ae94bdb5a0f6c11a4c438132545b4df80
RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8
RNFBApp: 5d3d1b9d563857c68d161a3226d9dd6d6be1a846
RNFBMessaging: 2895d9dfa1cfea663c26a1b5dfb5faf17ad096f8
Expand Down
39 changes: 38 additions & 1 deletion package-lock.json

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

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"react-native-file-viewer": "^2.1.5",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.20.2",
"react-native-get-random-values": "^1.11.0",
"react-native-html-to-pdf": "^0.12.0",
"react-native-image-crop-picker": "^0.41.2",
"react-native-keyboard-accessory": "^0.1.16",
Expand All @@ -92,7 +93,8 @@
"react-string-replace": "^1.1.1",
"react-usestateref": "^1.0.9",
"semver": "^7.6.3",
"superjson": "^2.2.1"
"superjson": "^2.2.1",
"uuid": "^11.0.5"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down Expand Up @@ -123,6 +125,7 @@
"@types/react-native-html-to-pdf": "^0.8.3",
"@types/react-native-video": "^5.0.14",
"@types/react-test-renderer": "^18.0.0",
"@types/uuid": "^10.0.0",
"babel-jest": "^29.6.3",
"babel-plugin-module-resolver": "^5.0.2",
"eslint": "^8.19.0",
Expand Down
9 changes: 7 additions & 2 deletions src/core/providers/ApiProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
import { useFeedbackContext } from '../contexts/FeedbackContext';
import { usePreferencesContext } from '../contexts/PreferencesContext';
import { useSplashContext } from '../contexts/SplashContext';
import { NO_TOKEN, resetKeychain } from '../queries/authHooks.ts';

export const asyncStoragePersister = createAsyncStoragePersister({
key: 'polito-students.queries',
Expand All @@ -55,7 +56,7 @@ export const ApiProvider = ({ children }: PropsWithChildren) => {
async (error: unknown, client: QueryClient) => {
if (error instanceof ResponseError) {
if (error.response.status === 401) {
await Keychain.resetGenericPassword();
await resetKeychain();
setApiContext(c => ({
...c,
isLogged: false,
Expand Down Expand Up @@ -163,7 +164,11 @@ export const ApiProvider = ({ children }: PropsWithChildren) => {
.then(keychainCredentials => {
let credentials = undefined;

if (username && keychainCredentials) {
if (
username &&
keychainCredentials &&
keychainCredentials.password !== NO_TOKEN
) {
credentials = {
username: username,
token: keychainCredentials.password,
Expand Down
40 changes: 32 additions & 8 deletions src/core/queries/authHooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Alert, Platform } from 'react-native';
import DeviceInfo from 'react-native-device-info';
import 'react-native-get-random-values';
import Keychain from 'react-native-keychain';

import { AuthApi, LoginRequest, SwitchCareerRequest } from '@polito/api-client';
Expand All @@ -8,6 +9,7 @@ import messaging from '@react-native-firebase/messaging';
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { t } from 'i18next';
import { v4 as uuidv4 } from 'uuid';

import { isEnvProduction } from '../../utils/env';
import { pluckData } from '../../utils/queries';
Expand All @@ -16,10 +18,19 @@ import { usePreferencesContext } from '../contexts/PreferencesContext';
import { UnsupportedUserTypeError } from '../errors/UnsupportedUserTypeError';
import { asyncStoragePersister } from '../providers/ApiProvider';

export const NO_TOKEN = '__EMPTY__';

const useAuthClient = (): AuthApi => {
return new AuthApi();
};

export async function resetKeychain(): Promise<void> {
const credentials = await Keychain.getGenericPassword();
if (credentials) {
await Keychain.setGenericPassword(credentials.username, NO_TOKEN);
}
}

async function getFcmToken(): Promise<string | undefined> {
if (!isEnvProduction) return undefined;

Expand All @@ -32,16 +43,28 @@ async function getFcmToken(): Promise<string | undefined> {
return undefined;
}

const getClientId = async (): Promise<string> => {
try {
const credentials = await Keychain.getGenericPassword();
if (credentials && credentials.username) {
return credentials.username;
}
} catch (e) {
console.warn("Keychain couldn't be accessed!", e);
}
const clientId = uuidv4();
await Keychain.setGenericPassword(clientId, NO_TOKEN);
return clientId;
};

export const useLogin = () => {
const authClient = useAuthClient();
const { refreshContext } = useApiContext();
const { updatePreference } = usePreferencesContext();

return useMutation({
mutationFn: (dto: LoginRequest) => {
const client = { name: 'Students app', id: 'students-app' };

return Promise.all([
getClientId(),
DeviceInfo.getDeviceName(),
DeviceInfo.getModel(),
DeviceInfo.getManufacturer(),
Expand All @@ -51,6 +74,7 @@ export const useLogin = () => {
])
.then(
([
id,
name,
model,
manufacturer,
Expand All @@ -66,9 +90,10 @@ export const useLogin = () => {
manufacturer,
};
dto.client = {
...client,
name: 'students-app',
buildNumber,
appVersion,
id,
fcmRegistrationToken,
};
dto.preferences = { ...dto.preferences };
Expand All @@ -86,10 +111,9 @@ export const useLogin = () => {
});
},
onSuccess: async data => {
const { token, clientId, username } = data;
const { token, clientId: clientIdentifier, username } = data;
refreshContext({ username, token });
updatePreference('username', username);
await Keychain.setGenericPassword(clientId, token);
await Keychain.setGenericPassword(clientIdentifier, token);
},
});
};
Expand All @@ -105,7 +129,7 @@ export const useLogout = () => {
refreshContext();
asyncStoragePersister.removeClient();
queryClient.removeQueries();
await Keychain.resetGenericPassword();
await resetKeychain();
},
});
};
Expand Down