🔐 feat: Add Signed CloudFront File Downloads (#12970)

* feat: add signed CloudFront downloads

* fix: preserve local IdP avatar paths

* fix: address signed download review findings

* fix: harden CloudFront cookie scope validation

* fix: preserve URL save API compatibility

* fix: store CDN SSO avatars under shared prefix

* fix: Harden CloudFront tenant file access

* fix: Preserve CloudFront download compatibility

* fix: Address CloudFront review follow-ups

* fix: Preserve file URL fallback user paths

* fix: Address download review hardening

* fix: Use file owner for S3 RAG cleanup

* fix: Address final download review nits

* fix: Clear stale avatar CloudFront cookies

* fix: Align download filename helpers with dev

* fix: Address final CloudFront review follow-ups

* fix: Stream S3 URL uploads

* fix: Set S3 stream upload length

* fix: Preserve download metadata filepath

* fix: Avoid remote content length for stream uploads

* fix: Use bounded multipart URL uploads

* fix: Harden S3 filename boundaries
This commit is contained in:
Danny Avila 2026-05-06 19:48:30 -04:00 • committed by GitHub
parent 4bd5630651
commit 9c81792d25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 3080 additions and 289 deletions

View file

@ -16,7 +16,7 @@ const loginController = async (req, res) => {
const { password: _p, totpSecret: _t, __v, ...user } = req.user;
user.id = user._id.toString();
const token = await setAuthTokens(req.user._id, res);
const token = await setAuthTokens(req.user._id, res, null, req);
return res.status(200).send({ token, user });
} catch (err) {

View file

@ -44,7 +44,10 @@ const logoutController = async (req, res) => {
res.clearCookie('openid_id_token');
res.clearCookie('openid_user_id');
res.clearCookie('token_provider');
clearCloudFrontCookies(res);
clearCloudFrontCookies(res, {
userId: req.user?.id ?? req.user?._id?.toString?.(),
tenantId: req.user?.tenantId,
});
const response = { message };
if (
isOpenIdUser &&

View file

@ -261,12 +261,15 @@ describe('LogoutController', () => {
});
it('calls clearCloudFrontCookies on successful logout', async () => {
const req = buildReq();
const req = buildReq({ user: { _id: 'user1', tenantId: 'tenantA' } });
const res = buildRes();
await logoutController(req, res);
expect(mockClearCloudFrontCookies).toHaveBeenCalledWith(res);
expect(mockClearCloudFrontCookies).toHaveBeenCalledWith(res, {
userId: 'user1',
tenantId: 'tenantA',
});
});
});

View file

@ -50,7 +50,7 @@ const verify2FAWithTempToken = async (req, res) => {
delete userData.backupCodes;
userData.id = user._id.toString();
const authToken = await setAuthTokens(user._id, res);
const authToken = await setAuthTokens(user._id, res, null, req);
return res.status(200).json({ token: authToken, user: userData });
} catch (err) {
logger.error('[verify2FAWithTempToken]', err);

View file

@ -68,9 +68,12 @@ function createOAuthHandler(redirectUri = domains.client) {
isEnabled(process.env.OPENID_REUSE_TOKENS) === true
) {
await syncUserEntraGroupMemberships(req.user, req.user.tokenset.access_token);
setOpenIDAuthTokens(req.user.tokenset, req, res, req.user._id.toString());
setOpenIDAuthTokens(req.user.tokenset, req, res, {
userId: req.user._id.toString(),
tenantId: req.user.tenantId,
});
} else {
await setAuthTokens(req.user._id, res);
await setAuthTokens(req.user._id, res, null, req);
}
res.redirect(redirectUri);
} catch (err) {