add storage

This commit is contained in:
TheCactusBlue 2025-03-25 16:07:43 -07:00
parent 2c6bb4b4ee
commit 25c05014b5
5 changed files with 113 additions and 3 deletions

View File

@ -19,6 +19,7 @@ import { InternalProjectsCrud, ProjectsCrud } from './crud/projects';
import { SessionsCrud } from './crud/sessions';
import { TeamInvitationCrud } from './crud/team-invitation';
import { TeamMemberProfilesCrud } from './crud/team-member-profiles';
import { ProjectPermissionsCrud } from './crud/project-permissions';
import { TeamPermissionsCrud } from './crud/team-permissions';
import { TeamsCrud } from './crud/teams';
@ -1183,6 +1184,21 @@ export class StackClientInterface {
return result.items;
}
async listCurrentUserProjectPermissions(
options: {
recursive: boolean,
},
session: InternalSession
): Promise<ProjectPermissionsCrud['Client']['Read'][]> {
const response = await this.sendClientRequest(
`/project-permissions?user_id=me&recursive=${options.recursive}`,
{},
session,
);
const result = await response.json() as ProjectPermissionsCrud['Client']['List'];
return result.items;
}
async listCurrentUserTeams(session: InternalSession): Promise<TeamsCrud["Client"]["Read"][]> {
const response = await this.sendClientRequest(
"/teams?user_id=me",

View File

@ -15,6 +15,7 @@ import { SessionsCrud } from "./crud/sessions";
import { TeamInvitationCrud } from "./crud/team-invitation";
import { TeamMemberProfilesCrud } from "./crud/team-member-profiles";
import { TeamMembershipsCrud } from "./crud/team-memberships";
import { ProjectPermissionsCrud } from "./crud/project-permissions";
import { TeamPermissionsCrud } from "./crud/team-permissions";
import { TeamsCrud } from "./crud/teams";
import { UsersCrud } from "./crud/users";
@ -195,6 +196,25 @@ export class StackServerInterface extends StackClientInterface {
return result.items;
}
async listServerProjectPermissions(
options: {
userId?: string,
recursive: boolean,
},
session: InternalSession | null,
): Promise<ProjectPermissionsCrud['Server']['Read'][]> {
const response = await this.sendServerRequest(
`/project-permissions?${new URLSearchParams(filterUndefined({
user_id: options.userId,
recursive: options.recursive.toString(),
}))}`,
{},
session,
);
const result = await response.json() as ProjectPermissionsCrud['Server']['List'];
return result.items;
}
async listServerUsers(options: {
cursor?: string,
limit?: number,

View File

@ -46,6 +46,7 @@ let isReactServer = false;
// IF_PLATFORM next
import * as sc from "@stackframe/stack-sc";
import { cookies } from '@stackframe/stack-sc';
import { ProjectPermissionsCrud } from "@stackframe/stack-shared/dist/interface/crud/project-permissions";
isReactServer = sc.isReactServer;
// NextNavigation.useRouter does not exist in react-server environments and some bundlers try to be helpful and throw a warning. Ignore the warning.
@ -115,6 +116,12 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
>(async (session, [teamId, recursive]) => {
return await this._interface.listCurrentUserTeamPermissions({ teamId, recursive }, session);
});
private readonly _currentUserProjectPermissionsCache = createCacheBySession<
[boolean],
ProjectPermissionsCrud['Client']['Read'][]
>(async (session, [recursive]) => {
return await this._interface.listCurrentUserProjectPermissions({ recursive }, session);
});
private readonly _currentUserTeamsCache = createCacheBySession(async (session) => {
return await this._interface.listCurrentUserTeams(session);
});
@ -584,7 +591,7 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
};
}
protected _clientTeamPermissionFromCrud(crud: TeamPermissionsCrud['Client']['Read']): TeamPermission {
protected _clientTeamPermissionFromCrud(crud: TeamPermissionsCrud['Client']['Read'] | ProjectPermissionsCrud['Client']['Read']): TeamPermission {
return {
id: crud.id,
};
@ -868,18 +875,32 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
const permissions = Result.orThrow(await app._currentUserPermissionsCache.getOrWait([session, scope.id, recursive], "write-only"));
return permissions.map((crud) => app._clientTeamPermissionFromCrud(crud));
},
async listProjectPermissions(options?: { recursive?: boolean }): Promise<TeamPermission[]> {
const recursive = options?.recursive ?? true;
const permissions = Result.orThrow(await app._currentUserProjectPermissionsCache.getOrWait([session, recursive], "write-only"));
return permissions.map((crud) => app._clientTeamPermissionFromCrud(crud));
},
// IF_PLATFORM react-like
usePermissions(scope: Team, options?: { recursive?: boolean }): TeamPermission[] {
const recursive = options?.recursive ?? true;
const permissions = useAsyncCache(app._currentUserPermissionsCache, [session, scope.id, recursive] as const, "user.usePermissions()");
return useMemo(() => permissions.map((crud) => app._clientTeamPermissionFromCrud(crud)), [permissions]);
},
useProjectPermissions(options?: { recursive?: boolean }): TeamPermission[] {
const recursive = options?.recursive ?? true;
const permissions = useAsyncCache(app._currentUserProjectPermissionsCache, [session, recursive] as const, "user.useProjectPermissions()");
return useMemo(() => permissions.map((crud) => app._clientTeamPermissionFromCrud(crud)), [permissions]);
},
// END_PLATFORM
// IF_PLATFORM react-like
usePermission(scope: Team, permissionId: string): TeamPermission | null {
const permissions = this.usePermissions(scope);
return useMemo(() => permissions.find((p) => p.id === permissionId) ?? null, [permissions, permissionId]);
},
useProjectPermission(permissionId: string): TeamPermission | null {
const permissions = this.useProjectPermissions();
return useMemo(() => permissions.find((p) => p.id === permissionId) ?? null, [permissions, permissionId]);
},
// END_PLATFORM
async getPermission(scope: Team, permissionId: string): Promise<TeamPermission | null> {
const permissions = await this.listPermissions(scope);
@ -888,6 +909,13 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
async hasPermission(scope: Team, permissionId: string): Promise<boolean> {
return (await this.getPermission(scope, permissionId)) !== null;
},
async getProjectPermission(permissionId: string): Promise<TeamPermission | null> {
const permissions = await this.listProjectPermissions();
return permissions.find((p) => p.id === permissionId) ?? null;
},
async hasProjectPermission(permissionId: string): Promise<boolean> {
return (await this.getProjectPermission(permissionId)) !== null;
},
async update(update) {
return await app._updateClientUser(update, session);
},

View File

@ -3,7 +3,7 @@ import { ContactChannelsCrud } from "@stackframe/stack-shared/dist/interface/cru
import { TeamInvitationCrud } from "@stackframe/stack-shared/dist/interface/crud/team-invitation";
import { TeamMemberProfilesCrud } from "@stackframe/stack-shared/dist/interface/crud/team-member-profiles";
import { TeamPermissionDefinitionsCrud, TeamPermissionsCrud } from "@stackframe/stack-shared/dist/interface/crud/team-permissions";
import { ProjectPermissionDefinitionsCrud } from "@stackframe/stack-shared/dist/interface/crud/project-permissions";
import { ProjectPermissionDefinitionsCrud, ProjectPermissionsCrud } from "@stackframe/stack-shared/dist/interface/crud/project-permissions";
import { TeamsCrud } from "@stackframe/stack-shared/dist/interface/crud/teams";
import { UsersCrud } from "@stackframe/stack-shared/dist/interface/crud/users";
import { InternalSession } from "@stackframe/stack-shared/dist/sessions";
@ -61,6 +61,12 @@ export class _StackServerAppImplIncomplete<HasTokenStore extends boolean, Projec
>(async ([teamId, userId, recursive]) => {
return await this._interface.listServerTeamPermissions({ teamId, userId, recursive }, null);
});
private readonly _serverUserProjectPermissionsCache = createCache<
[string, boolean],
ProjectPermissionsCrud['Server']['Read'][]
>(async ([userId, recursive]) => {
return await this._interface.listServerProjectPermissions({ userId, recursive }, null);
});
private readonly _serverUserOAuthConnectionAccessTokensCache = createCache<[string, string, string], { accessToken: string } | null>(
async ([userId, providerId, scope]) => {
try {
@ -341,6 +347,31 @@ export class _StackServerAppImplIncomplete<HasTokenStore extends boolean, Projec
async hasPermission(scope: Team, permissionId: string): Promise<boolean> {
return await this.getPermission(scope, permissionId) !== null;
},
async listProjectPermissions(options?: { recursive?: boolean }): Promise<AdminTeamPermission[]> {
const recursive = options?.recursive ?? true;
const permissions = Result.orThrow(await app._serverUserProjectPermissionsCache.getOrWait([crud.id, recursive], "write-only"));
return permissions.map((crud) => app._serverPermissionFromCrud(crud));
},
// IF_PLATFORM react-like
useProjectPermissions(options?: { recursive?: boolean }): AdminTeamPermission[] {
const recursive = options?.recursive ?? true;
const permissions = useAsyncCache(app._serverUserProjectPermissionsCache, [crud.id, recursive] as const, "user.useProjectPermissions()");
return useMemo(() => permissions.map((crud) => app._serverPermissionFromCrud(crud)), [permissions]);
},
// END_PLATFORM
async getProjectPermission(permissionId: string): Promise<AdminTeamPermission | null> {
const permissions = await this.listProjectPermissions();
return permissions.find((p) => p.id === permissionId) ?? null;
},
// IF_PLATFORM react-like
useProjectPermission(permissionId: string): AdminTeamPermission | null {
const permissions = this.useProjectPermissions();
return useMemo(() => permissions.find((p) => p.id === permissionId) ?? null, [permissions, permissionId]);
},
// END_PLATFORM
async hasProjectPermission(permissionId: string): Promise<boolean> {
return await this.getProjectPermission(permissionId) !== null;
},
async update(update: ServerUserUpdateOptions) {
await app._updateServerUser(crud.id, update);
},
@ -626,7 +657,7 @@ export class _StackServerAppImplIncomplete<HasTokenStore extends boolean, Projec
}
// END_PLATFORM
_serverPermissionFromCrud(crud: TeamPermissionsCrud['Server']['Read']): AdminTeamPermission {
_serverPermissionFromCrud(crud: TeamPermissionsCrud['Server']['Read'] | ProjectPermissionsCrud['Server']['Read']): AdminTeamPermission {
return {
id: crud.id,
};

View File

@ -187,6 +187,13 @@ export type UserExtra = {
// END_PLATFORM
hasPermission(scope: Team, permissionId: string): Promise<boolean>,
getProjectPermission(permissionId: string): Promise<TeamPermission | null>,
hasProjectPermission(permissionId: string): Promise<boolean>,
listProjectPermissions(options?: { recursive?: boolean }): Promise<TeamPermission[]>,
// IF_PLATFORM react-like
useProjectPermissions(options?: { recursive?: boolean }): TeamPermission[],
useProjectPermission(permissionId: string): TeamPermission | null,
// END_PLATFORM
readonly selectedTeam: Team | null,
setSelectedTeam(team: Team | null): Promise<void>,
@ -269,6 +276,14 @@ export type ServerBaseUser = {
grantPermission(scope: Team, permissionId: string): Promise<void>,
revokePermission(scope: Team, permissionId: string): Promise<void>,
getProjectPermission(permissionId: string): Promise<TeamPermission | null>,
hasProjectPermission(permissionId: string): Promise<boolean>,
listProjectPermissions(options?: { recursive?: boolean }): Promise<TeamPermission[]>,
// IF_PLATFORM react-like
useProjectPermissions(options?: { recursive?: boolean }): TeamPermission[],
useProjectPermission(permissionId: string): TeamPermission | null,
// END_PLATFORM
/**
* Creates a new session object with a refresh token for this user. Can be used to impersonate them.