11import type { ExecutionContext } from '@nestjs/common' ;
22import { ForbiddenException , Injectable , Logger , UnauthorizedException } from '@nestjs/common' ;
33import { Reflector } from '@nestjs/core' ;
4- import { ANONYMOUS_USER_ID , HttpErrorCode , isAnonymous , type Action } from '@teable/core' ;
4+ import { ANONYMOUS_USER_ID , HttpErrorCode , IdPrefix , isAnonymous , type Action } from '@teable/core' ;
55import cookie from 'cookie' ;
66import { ClsService } from 'nestjs-cls' ;
77import { CustomHttpException } from '../../../custom.exception' ;
@@ -155,12 +155,15 @@ export class PermissionGuard {
155155 resourceId ,
156156 permissions
157157 ) ;
158- // Set user to anonymous for share context
159- this . cls . set ( 'user' , {
160- id : ANONYMOUS_USER_ID ,
161- name : ANONYMOUS_USER_ID ,
162- email : '' ,
163- } ) ;
158+ // Preserve logged-in user identity for allowEdit; fall back to anonymous
159+ const currentUserId = this . cls . get ( 'user.id' ) ;
160+ if ( ! currentUserId || isAnonymous ( currentUserId ) ) {
161+ this . cls . set ( 'user' , {
162+ id : ANONYMOUS_USER_ID ,
163+ name : ANONYMOUS_USER_ID ,
164+ email : '' ,
165+ } ) ;
166+ }
164167 this . cls . set ( 'permissions' , ownPermissions ) ;
165168 return true ;
166169 }
@@ -297,6 +300,22 @@ export class PermissionGuard {
297300 if ( ! shareId ) {
298301 return undefined ;
299302 }
303+ // Skip share path for endpoints without @Permissions (e.g. /user/me),
304+ // otherwise baseSharePermissionCheck throws ForbiddenException.
305+ const permissions = this . reflector . getAllAndOverride < Action [ ] | undefined > ( PERMISSIONS_KEY , [
306+ context . getHandler ( ) ,
307+ context . getClass ( ) ,
308+ ] ) ;
309+ if ( ! permissions ?. length ) {
310+ return undefined ;
311+ }
312+ // Skip share check when the target resource is outside the share scope.
313+ // e.g. space-level endpoints (GET /space, POST /share/:id/base/copy with spaceId in body)
314+ // should use the user's own permissions, not the share's.
315+ const resourceId = this . getResourceId ( context ) || this . defaultResourceId ( context ) ;
316+ if ( ! resourceId || resourceId . startsWith ( IdPrefix . Space ) ) {
317+ return undefined ;
318+ }
300319 return await this . baseSharePermissionCheck ( context , shareId ) ;
301320 }
302321
@@ -383,9 +402,10 @@ export class PermissionGuard {
383402 *
384403 * Priority flow:
385404 * 1. RESOURCE-level: exclusively use resource-specific auth (base share > template)
386- * 2. Early base share check for PUBLIC or anonymous requests when header is present
405+ * 2. Share link check — when share header is present, share permissions are the ceiling
406+ * for ALL users (anonymous or authenticated), so personal role never exceeds the link
387407 * 3. Anonymous user handling (template / USER-level)
388- * 4. Authenticated user: standard check, with fallback for PUBLIC endpoints
408+ * 4. Authenticated user: standard check, with PUBLIC fallback
389409 */
390410 protected async permissionCheckWithPublicFallback (
391411 context : ExecutionContext ,
@@ -406,10 +426,8 @@ export class PermissionGuard {
406426 // No valid resource auth header — fall through to normal checks
407427 }
408428
409- // 2. Early base share check for PUBLIC or anonymous requests
410- const shouldTryBaseShareEarly =
411- baseShareHeader && ( allowAnonymousType === AllowAnonymousType . PUBLIC || this . isAnonymous ( ) ) ;
412- if ( shouldTryBaseShareEarly ) {
429+ // 2. Share link — permissions are bounded by the link, regardless of user role
430+ if ( baseShareHeader ) {
413431 const result = await this . tryBaseSharePermissionCheck ( context , baseShareHeader ) ;
414432 if ( result !== undefined ) return result ;
415433 }
@@ -419,7 +437,7 @@ export class PermissionGuard {
419437 return this . resolveAnonymousPermission ( context , allowAnonymousType ) ;
420438 }
421439
422- // 4. Authenticated user: standard check, with fallback for PUBLIC endpoints
440+ // 4. Authenticated user: standard check, with PUBLIC fallback
423441 try {
424442 return await permissionCheck ( ) ;
425443 } catch ( error ) {
0 commit comments