Skip to content

Commit e72693b

Browse files
committed
improvement(cli-auth): reuse the api-key access schema and tidy handoff validation
1 parent 5f3d4b6 commit e72693b

6 files changed

Lines changed: 28 additions & 19 deletions

File tree

‎apps/sim/app/api/cli/auth/approve/route.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ const logger = createLogger('CliAuthApproveAPI')
2828
*
2929
* That makes the approval record the sole carrier of the decision. The poll body
3030
* is a request id and a secret and nothing else, so it cannot assert a scope, a
31-
* workspace, a binding, an access level, or a lifetime of its own — a refusal here writes no record, and a
32-
* poll driven directly against the same request id answers `pending` forever.
31+
* workspace, a binding, an access level, or a lifetime of its own — a refusal
32+
* here writes no record, and a poll driven directly against the same request id
33+
* answers `pending` forever.
3334
*/
3435
export const POST = withRouteHandler(async (request: NextRequest) => {
3536
const session = await getSession()

‎apps/sim/app/api/cli/auth/poll/route.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ApiKeyAccess } from '@sim/auth/principal'
12
import { createLogger } from '@sim/logger'
23
import { type NextRequest, NextResponse } from 'next/server'
34
import { pollCliAuthContract } from '@/lib/api/contracts'
@@ -67,7 +68,7 @@ async function mintForGrant(grant: ApprovalGrant): Promise<
6768
| {
6869
ok: true
6970
key: { id: string; apiKey: string }
70-
settings?: { access: 'read' | 'write'; expiresAt: string | null }
71+
settings?: { access: ApiKeyAccess; expiresAt: string | null }
7172
}
7273
| { ok: false; status: number; message: string }
7374
> {

‎apps/sim/app/cli/auth/cli-auth-request.ts‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,18 @@ export function resolveCliAuthRequest({
7272
return { valid: false, reason: 'Key access and expiry only apply to Sim API keys.' }
7373
}
7474

75-
const keyAccess: ApiKeyAccess | null =
76-
access === null ? 'write' : access === 'read' || access === 'write' ? access : null
77-
if (!keyAccess) {
75+
const keyAccess = access ?? 'write'
76+
if (keyAccess !== 'read' && keyAccess !== 'write') {
7877
return { valid: false, reason: 'The requested key access is malformed.' }
7978
}
8079

8180
const expiresInDays = expires === null ? null : Number(expires)
8281
if (
8382
expires !== null &&
84-
!(
85-
/^\d+$/.test(expires) &&
86-
Number(expires) >= CLI_AUTH_MIN_EXPIRY_DAYS &&
87-
Number(expires) <= CLI_AUTH_MAX_EXPIRY_DAYS
88-
)
83+
(!/^\d+$/.test(expires) ||
84+
expiresInDays === null ||
85+
expiresInDays < CLI_AUTH_MIN_EXPIRY_DAYS ||
86+
expiresInDays > CLI_AUTH_MAX_EXPIRY_DAYS)
8987
) {
9088
return {
9189
valid: false,

‎apps/sim/lib/api/contracts/api-keys.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
44
/** One day in milliseconds: a key's expiry options and its creation bounds are counted in days. */
55
export const DAY_MS = 24 * 60 * 60 * 1000
66

7-
const apiKeyAccessSchema = z.enum(['read', 'write'])
7+
export const apiKeyAccessSchema = z.enum(['read', 'write'])
88
export type ApiKeyAccessLevel = z.output<typeof apiKeyAccessSchema>
99

1010
const apiKeyMetadataSchema = z.object({

‎apps/sim/lib/api/contracts/cli-auth.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from 'zod'
2+
import { apiKeyAccessSchema } from '@/lib/api/contracts/api-keys'
23
import { workspaceIdSchema } from '@/lib/api/contracts/primitives'
34
import { defineRouteContract } from '@/lib/api/contracts/types'
45

@@ -29,9 +30,6 @@ const base64Url43 = (message: string) => z.string().regex(/^[A-Za-z0-9\-_]{43}$/
2930
export const cliAuthScopeSchema = z.enum(['copilot', 'platform']).default('copilot')
3031
export type CliAuthScope = z.output<typeof cliAuthScopeSchema>
3132

32-
/** Access a minted platform key carries: full (`write`) or read-only (`read`). */
33-
const cliAuthKeyAccessSchema = z.enum(['read', 'write'])
34-
3533
/**
3634
* Bounds on a minted platform key's lifetime, in whole days. The same window
3735
* every other key-creation surface allows: one day to one year.
@@ -72,7 +70,7 @@ export const approveCliAuthBodySchema = z.object({
7270
* approves; recording it here, rather than reading it at poll time, is what
7371
* keeps the unauthenticated poll from widening it.
7472
*/
75-
access: cliAuthKeyAccessSchema.optional().default('write'),
73+
access: apiKeyAccessSchema.optional().default('write'),
7674
/** Platform scope only: days until the minted key expires. Absent means it never expires. */
7775
expiresInDays: cliAuthExpiresInDaysSchema.optional(),
7876
})
@@ -132,7 +130,7 @@ export const pollCliAuthContract = defineRouteContract({
132130
* A terminal that asked for read-only must check this rather than
133131
* assume it — a server that predates the field mints full access.
134132
*/
135-
access: cliAuthKeyAccessSchema.optional(),
133+
access: apiKeyAccessSchema.optional(),
136134
/** Platform scope only: when the key expires, or null when it never does. */
137135
expiresAt: z.string().nullable().optional(),
138136
}),

‎apps/sim/lib/workspaces/application/update-workspace.ts‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
resolveActiveWorkspaceApplicationContext,
1313
} from '@/lib/workspaces/application/context'
1414
import { workspaceUpdateOperations } from '@/lib/workspaces/application/operations'
15-
import { countPublicWorkspaceMembers } from '@/lib/workspaces/public-queries'
15+
import {
16+
countPublicWorkspaceMembers,
17+
type PublicWorkspaceDetail,
18+
} from '@/lib/workspaces/public-queries'
1619
import {
1720
applyWorkspaceUpdate,
1821
readWorkspaceIdentityRecord,
@@ -111,7 +114,15 @@ export const updateWorkspaceIdentity = defineAuthorizedWorkspaceUseCase({
111114
...result,
112115
patch,
113116
identity: presentIdentity(result.updated),
114-
workspace: { id, name, color, logoUrl, memberCount, createdAt, updatedAt },
117+
workspace: {
118+
id,
119+
name,
120+
color,
121+
logoUrl,
122+
memberCount,
123+
createdAt,
124+
updatedAt,
125+
} satisfies PublicWorkspaceDetail,
115126
}
116127
},
117128
projectAudit: ({ result }) => projectUpdateAudit(result.patch, result),

0 commit comments

Comments
 (0)