Skip to content

Commit 902abe2

Browse files
committed
add server endpoint to fetch user images
1 parent 4bc18dc commit 902abe2

File tree

3 files changed

+92
-2
lines changed

3 files changed

+92
-2
lines changed

server/README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Back-end server for codam-web-greeter
2-
The back-end server provides data for the greeter to display, such as events and exams on [42's Intranet](https://intra.42.fr/).
2+
The back-end server provides data for the greeter to display, such as events and exams on [42's Intranet](https://intra.42.fr/). It also provides an endpoint to fetch an Intra user's profile picture.
3+
4+
> ⚠️ You should make sure this server is only accessible on your local campus network, to avoid leaking personal data to the outside world. It is also recommended to configure your campus's access-control lists in a way that allows only campus computers to access this server. If you do not know how to do this, ask your campus's network administrator for help.
35
46

57
## Running the server
@@ -126,3 +128,33 @@ Request data to be displayed by the greeter for the given hostname. If no hostna
126128
"message": "A custom message to display on the login screen\nIt supports *bold* and _italic_ text",
127129
}
128130
```
131+
132+
### `/api/user/:login/.face`
133+
Redirects to the user's profile picture served by Intranet's CDN. This endpoint can be used by the greeter to display the user's profile picture on the lock screen.
134+
135+
#### Example return data
136+
A redirect to a raw image through a 300 status code, or a 404 error.
137+
138+
### `/api/exam_mode_hosts`
139+
Returns a list of hostnames that are currently in exam mode. A host can only be displayed if it was able to fetch its configuration from the `/api/config/:hostname` endpoint at least once since the server started.
140+
141+
#### Example return data
142+
```json
143+
{
144+
"exam_mode_hosts": [
145+
"f1r1s1.codam.nl",
146+
"f1r2s1.codam.nl",
147+
"f1r3s1.codam.nl"
148+
],
149+
"message": "Exams in progress: 42042",
150+
"status": "ok"
151+
}
152+
```
153+
154+
```json
155+
{
156+
"exam_mode_hosts": [],
157+
"message": "No exams are currently running",
158+
"status": "ok"
159+
}
160+
```

server/src/intra.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,39 @@ export const fetchExams = async function(api: Fast42): Promise<Exam42[]> {
119119
return [];
120120
}
121121
};
122+
123+
export const fetchUserImage = async function(api: Fast42, login: string): Promise<string> {
124+
try {
125+
const req = await api.get(`/users/`, {
126+
'filter[login]': login, // Filtering instead of querying for the specific user is faster
127+
});
128+
if (req.status == 429) {
129+
throw new Error('Intra API rate limit exceeded');
130+
}
131+
if (req.ok) {
132+
const data = await req.json();
133+
if (data.length == 0) {
134+
throw new Error('User not found on Intra');
135+
}
136+
const user = data[0];
137+
if (user.image) {
138+
if (user.image.versions && user.image.versions.large) {
139+
return user.image.versions.large;
140+
}
141+
else {
142+
return user.image.link; // This one should always exist
143+
}
144+
}
145+
else {
146+
throw new Error('User has no image set on Intra');
147+
}
148+
}
149+
else {
150+
throw new Error(`Intra API error: ${req.status} ${req.statusText}`);
151+
}
152+
}
153+
catch (err) {
154+
console.log(`Failed fetching user image for ${login}: ${err}`);
155+
return '';
156+
}
157+
}

server/src/routes.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Express } from 'express';
22
import { Config, ConfigError, Event42, Exam42 } from './interfaces';
33
import { getCurrentExams, getExamForHostName, getHostNameFromRequest, hostNameToIp, examAvailableForHost, getMessageForHostName } from './utils';
4-
import { fetchEvents, fetchExams } from './intra';
4+
import { fetchEvents, fetchExams, fetchUserImage } from './intra';
55

66
// Intra API
77
import Fast42 from '@codam/fast42';
@@ -106,6 +106,28 @@ export default (app: Express) => {
106106
cache.set('examModeHosts', ret, 5); // 5 second cache
107107
return res.send(ret);
108108
});
109+
110+
app.get('/api/user/:login/.face', async (req, res) => {
111+
const login = req.params.login;
112+
if (!login) {
113+
return res.status(400).send({ error: 'No login provided' });
114+
}
115+
if (!api) {
116+
return res.status(503).send({ error: 'Intra API not initialized' });
117+
}
118+
if (cache.has(`user-image-${login}`)) {
119+
const imageUrl = cache.get<string>(`user-image-${login}`);
120+
if (imageUrl) {
121+
return res.redirect(imageUrl);
122+
}
123+
}
124+
const imageUrl = await fetchUserImage(api, login);
125+
if (!imageUrl) {
126+
return res.status(404).send({ error: 'User not found or no image set' });
127+
}
128+
cache.set(`user-image-${login}`, imageUrl, cacheTTL); // Cache the image URL
129+
return res.redirect(imageUrl);
130+
});
109131
};
110132

111133
const setUpIntraAPI = async function() {

0 commit comments

Comments
 (0)