mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-22 21:26:04 +00:00
fix: puter site middleware host (#2594)
This commit is contained in:
@@ -127,6 +127,13 @@ function getSubdomainFromHostedRequest (req) {
|
||||
return host.split('.')[0] || '';
|
||||
}
|
||||
|
||||
function getRequestedPrivateHost (req) {
|
||||
const normalizedRequestHost = normalizeConfiguredHostname(req.hostname);
|
||||
if ( ! normalizedRequestHost ) return undefined;
|
||||
if ( ! hostMatchesPrivateDomain(normalizedRequestHost) ) return undefined;
|
||||
return normalizedRequestHost;
|
||||
}
|
||||
|
||||
function buildPrivateHostRedirectUrl (req, app) {
|
||||
if ( ! app ) {
|
||||
return null;
|
||||
@@ -388,6 +395,7 @@ async function resolvePrivateIdentity ({ req, services, appUid }) {
|
||||
const privateCookieName = authService.getPrivateAssetCookieName();
|
||||
const privateCookieToken = req.cookies?.[privateCookieName];
|
||||
const privateAppSubdomain = getSubdomainFromHostedRequest(req) || undefined;
|
||||
const requestedPrivateHost = getRequestedPrivateHost(req);
|
||||
const hasPrivateCookie = typeof privateCookieToken === 'string' && !!privateCookieToken;
|
||||
let hasInvalidPrivateCookie = false;
|
||||
|
||||
@@ -396,12 +404,14 @@ async function resolvePrivateIdentity ({ req, services, appUid }) {
|
||||
const claims = authService.verifyPrivateAssetToken(privateCookieToken, {
|
||||
expectedAppUid: appUid,
|
||||
expectedSubdomain: privateAppSubdomain,
|
||||
expectedPrivateHost: requestedPrivateHost,
|
||||
});
|
||||
return {
|
||||
source: 'private-cookie',
|
||||
userUid: claims.userUid,
|
||||
sessionUuid: claims.sessionUuid,
|
||||
subdomain: claims.subdomain || privateAppSubdomain,
|
||||
privateHost: claims.privateHost || requestedPrivateHost,
|
||||
hasValidPrivateCookie: true,
|
||||
hasPrivateCookie,
|
||||
hasInvalidPrivateCookie,
|
||||
@@ -422,6 +432,7 @@ async function resolvePrivateIdentity ({ req, services, appUid }) {
|
||||
source: 'session-cookie',
|
||||
...identity,
|
||||
subdomain: privateAppSubdomain,
|
||||
privateHost: requestedPrivateHost,
|
||||
hasValidPrivateCookie: false,
|
||||
hasPrivateCookie,
|
||||
hasInvalidPrivateCookie,
|
||||
@@ -465,6 +476,7 @@ async function resolvePrivateIdentity ({ req, services, appUid }) {
|
||||
source: 'bootstrap-token',
|
||||
...identity,
|
||||
subdomain: privateAppSubdomain,
|
||||
privateHost: requestedPrivateHost,
|
||||
hasValidPrivateCookie: false,
|
||||
hasPrivateCookie,
|
||||
hasInvalidPrivateCookie,
|
||||
@@ -487,6 +499,7 @@ async function resolvePrivateIdentity ({ req, services, appUid }) {
|
||||
userUid: undefined,
|
||||
sessionUuid: undefined,
|
||||
subdomain: privateAppSubdomain,
|
||||
privateHost: requestedPrivateHost,
|
||||
hasValidPrivateCookie: false,
|
||||
hasPrivateCookie,
|
||||
hasInvalidPrivateCookie,
|
||||
@@ -786,6 +799,7 @@ async function evaluatePrivateAppAccess ({ req, res, services, app, requestPath
|
||||
userUid: identity.userUid,
|
||||
sessionUuid: identity.sessionUuid,
|
||||
subdomain: identity.subdomain,
|
||||
privateHost: identity.privateHost,
|
||||
});
|
||||
res.cookie(
|
||||
authService.getPrivateAssetCookieName(),
|
||||
|
||||
@@ -953,6 +953,7 @@ describe('PuterSiteMiddleware', () => {
|
||||
userUid: 'user-allow-111',
|
||||
sessionUuid: 'session-allow-111',
|
||||
subdomain: 'paid',
|
||||
privateHost: 'paid.puter.dev',
|
||||
});
|
||||
expect(mockRes.cookie).toHaveBeenCalledWith(
|
||||
'puter.private.asset.token',
|
||||
|
||||
@@ -365,7 +365,14 @@ class AuthService extends BaseService {
|
||||
return normalizedSubdomain || undefined;
|
||||
}
|
||||
|
||||
createPrivateAssetToken ({ appUid, userUid, sessionUuid, subdomain, ttlSeconds } = {}) {
|
||||
normalizePrivateAssetHost (privateHost) {
|
||||
if ( typeof privateHost !== 'string' ) return undefined;
|
||||
const normalizedPrivateHost = privateHost.trim().toLowerCase().replace(/^\./, '');
|
||||
if ( ! normalizedPrivateHost ) return undefined;
|
||||
return normalizedPrivateHost;
|
||||
}
|
||||
|
||||
createPrivateAssetToken ({ appUid, userUid, sessionUuid, subdomain, privateHost, ttlSeconds } = {}) {
|
||||
if ( typeof appUid !== 'string' || !appUid.trim() ) {
|
||||
throw new Error('appUid is required to create private asset token.');
|
||||
}
|
||||
@@ -379,6 +386,10 @@ class AuthService extends BaseService {
|
||||
if ( subdomain !== undefined && !normalizedSubdomain ) {
|
||||
throw new Error('subdomain must be a non-empty string when provided.');
|
||||
}
|
||||
const normalizedPrivateHost = this.normalizePrivateAssetHost(privateHost);
|
||||
if ( privateHost !== undefined && !normalizedPrivateHost ) {
|
||||
throw new Error('privateHost must be a non-empty string when provided.');
|
||||
}
|
||||
|
||||
const effectiveTtlSeconds = this.resolvePositiveInteger(
|
||||
ttlSeconds,
|
||||
@@ -392,6 +403,7 @@ class AuthService extends BaseService {
|
||||
user_uid: userUid.trim(),
|
||||
...(sessionUuid ? { session: this.uuid_fpe.encrypt(sessionUuid) } : {}),
|
||||
...(normalizedSubdomain ? { subdomain: normalizedSubdomain } : {}),
|
||||
...(normalizedPrivateHost ? { private_host: normalizedPrivateHost } : {}),
|
||||
};
|
||||
|
||||
return this.tokenService.sign('auth', payload, {
|
||||
@@ -401,7 +413,7 @@ class AuthService extends BaseService {
|
||||
|
||||
verifyPrivateAssetToken (
|
||||
token,
|
||||
{ expectedAppUid, expectedUserUid, expectedSessionUuid, expectedSubdomain } = {},
|
||||
{ expectedAppUid, expectedUserUid, expectedSessionUuid, expectedSubdomain, expectedPrivateHost } = {},
|
||||
) {
|
||||
let decoded;
|
||||
try {
|
||||
@@ -440,6 +452,13 @@ class AuthService extends BaseService {
|
||||
}
|
||||
subdomain = decoded.subdomain.trim().toLowerCase();
|
||||
}
|
||||
let privateHost;
|
||||
if ( decoded.private_host !== undefined ) {
|
||||
if ( typeof decoded.private_host !== 'string' || !decoded.private_host.trim() ) {
|
||||
throw APIError.create('token_auth_failed');
|
||||
}
|
||||
privateHost = decoded.private_host.trim().toLowerCase();
|
||||
}
|
||||
|
||||
if ( expectedAppUid && decoded.app_uid !== expectedAppUid ) {
|
||||
throw APIError.create('token_auth_failed');
|
||||
@@ -461,12 +480,22 @@ class AuthService extends BaseService {
|
||||
throw APIError.create('token_auth_failed');
|
||||
}
|
||||
}
|
||||
const normalizedExpectedPrivateHost = this.normalizePrivateAssetHost(expectedPrivateHost);
|
||||
if ( expectedPrivateHost !== undefined && !normalizedExpectedPrivateHost ) {
|
||||
throw APIError.create('token_auth_failed');
|
||||
}
|
||||
if ( normalizedExpectedPrivateHost ) {
|
||||
if ( !privateHost || privateHost !== normalizedExpectedPrivateHost ) {
|
||||
throw APIError.create('token_auth_failed');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
appUid: decoded.app_uid,
|
||||
userUid: decoded.user_uid,
|
||||
sessionUuid,
|
||||
subdomain,
|
||||
privateHost,
|
||||
exp: decoded.exp,
|
||||
iat: decoded.iat,
|
||||
};
|
||||
|
||||
@@ -77,12 +77,14 @@ describe('AuthService private asset token helpers', () => {
|
||||
const userUid = '4b0cecf8-dd6a-4eb5-bcc4-c76cc7e8d7f0';
|
||||
const sessionUuid = 'f9000804-2fd3-4da5-819b-afc5296f90f7';
|
||||
const subdomain = 'beans';
|
||||
const privateHost = 'beans.puter.dev';
|
||||
|
||||
const token = authService.createPrivateAssetToken({
|
||||
appUid,
|
||||
userUid,
|
||||
sessionUuid,
|
||||
subdomain,
|
||||
privateHost,
|
||||
ttlSeconds: 120,
|
||||
});
|
||||
|
||||
@@ -91,12 +93,14 @@ describe('AuthService private asset token helpers', () => {
|
||||
expectedUserUid: userUid,
|
||||
expectedSessionUuid: sessionUuid,
|
||||
expectedSubdomain: subdomain,
|
||||
expectedPrivateHost: privateHost,
|
||||
});
|
||||
|
||||
expect(claims.appUid).toBe(appUid);
|
||||
expect(claims.userUid).toBe(userUid);
|
||||
expect(claims.sessionUuid).toBe(sessionUuid);
|
||||
expect(claims.subdomain).toBe(subdomain);
|
||||
expect(claims.privateHost).toBe(privateHost);
|
||||
expect(typeof claims.exp).toBe('number');
|
||||
});
|
||||
|
||||
@@ -106,6 +110,7 @@ describe('AuthService private asset token helpers', () => {
|
||||
appUid: 'app-9f1c10e3-9a7f-43fb-8671-af4918e65407',
|
||||
userUid: '9885b80e-1a14-4c8d-9e3f-4fa5915b1136',
|
||||
subdomain: 'beans',
|
||||
privateHost: 'beans.puter.dev',
|
||||
});
|
||||
|
||||
expect(() => authService.verifyPrivateAssetToken(token, {
|
||||
@@ -119,6 +124,10 @@ describe('AuthService private asset token helpers', () => {
|
||||
expect(() => authService.verifyPrivateAssetToken(token, {
|
||||
expectedSubdomain: 'other-app',
|
||||
})).toThrow();
|
||||
|
||||
expect(() => authService.verifyPrivateAssetToken(token, {
|
||||
expectedPrivateHost: 'other.puter.dev',
|
||||
})).toThrow();
|
||||
});
|
||||
|
||||
it('rejects non private-asset tokens', () => {
|
||||
|
||||
Reference in New Issue
Block a user