Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/app/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@
import { checkAppExists, defaultAppIconPath, getAppIconStoragePath, newIconPath } from '../api/app'
import { checkAlerts } from '../api/update'
import {
assertCliPermission,
createSupabaseClient,
findSavedKey,
formatError,
getAppId,
getConfig,
getContentType,
getOrganization,
getOrganizationWithPermission,
resolveUserIdFromApiKey,
sendEvent,
verifyUser,
} from '../utils'

export const reverseDomainRegex = /^[a-z0-9]+(\.[\w-]+)+$/i
Expand Down Expand Up @@ -90,15 +91,20 @@
ensureOptions(appId, options, silent)

const supabase = await createSupabaseClient(options.apikey!, options.supaHost, options.supaAnon)
const userId = await verifyUser(supabase, options.apikey!, ['write', 'all'])
const userId = await resolveUserIdFromApiKey(supabase, options.apikey!)

Check warning on line 94 in src/app/add.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since the receiver accepts the original type of the expression.

See more on https://sonarcloud.io/project/issues?id=Cap-go_capgo-cli&issues=AZ0mCQv7tOQ-SU_zo1s6&open=AZ0mCQv7tOQ-SU_zo1s6&pullRequest=571

await ensureAppDoesNotExist(supabase, appId, silent)

if (!organization)
organization = await getOrganization(supabase, ['admin', 'super_admin'])
organization = await getOrganizationWithPermission(supabase, options.apikey!, 'org.create_app')

Check warning on line 99 in src/app/add.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since the receiver accepts the original type of the expression.

See more on https://sonarcloud.io/project/issues?id=Cap-go_capgo-cli&issues=AZ0mCQv8tOQ-SU_zo1s7&open=AZ0mCQv8tOQ-SU_zo1s7&pullRequest=571

const organizationUid = organization.gid

await assertCliPermission(supabase, options.apikey!, 'org.create_app', { orgId: organizationUid }, {

Check warning on line 103 in src/app/add.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since the receiver accepts the original type of the expression.

See more on https://sonarcloud.io/project/issues?id=Cap-go_capgo-cli&issues=AZ0mCQv8tOQ-SU_zo1s8&open=AZ0mCQv8tOQ-SU_zo1s8&pullRequest=571
message: `Insufficient permissions to create an app in organization ${organizationUid}`,
silent,
})

let { name, icon } = options
name = name || extConfig.config?.appName || 'Unknown'
icon = icon || 'resources/icon.png'
Expand Down
4 changes: 2 additions & 2 deletions src/app/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
getConfig,
getOrganizationId,
OrganizationPerm,
resolveUserIdFromApiKey,
sendEvent,
verifyUser,
} from '../utils'

export async function deleteAppInternal(
Expand Down Expand Up @@ -39,7 +39,7 @@ export async function deleteAppInternal(
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
const userId = await verifyUser(supabase, options.apikey, ['write', 'all'])
const userId = await resolveUserIdFromApiKey(supabase, options.apikey)

await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.super_admin, silent)

Expand Down
24 changes: 5 additions & 19 deletions src/app/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Database } from '../types/supabase.types'
import { intro, log, outro } from '@clack/prompts'
import { Table } from '@sauber/table'
import { checkAlerts } from '../api/update'
import { createSupabaseClient, findSavedKey, getHumanDate, verifyUser } from '../utils'
import { createSupabaseClient, findSavedKey, getAccessibleAppsForApiKey, getHumanDate, resolveUserIdFromApiKey } from '../utils'

function displayApps(data: Database['public']['Tables']['apps']['Row'][]) {
const table = new Table()
Expand All @@ -18,22 +18,8 @@ function displayApps(data: Database['public']['Tables']['apps']['Row'][]) {
log.success(table.toString())
}

async function getActiveApps(
supabase: SupabaseClient<Database>,
silent: boolean,
) {
const { data, error: vError } = await supabase
.from('apps')
.select()
.order('created_at', { ascending: false })

if (vError) {
if (!silent)
log.error('Apps not found')
throw new Error('Apps not found')
}

return data ?? []
async function getActiveApps(supabase: SupabaseClient<Database>, apikey: string) {
return getAccessibleAppsForApiKey(supabase, apikey)
}

export async function listAppInternal(options: OptionsBase, silent = false) {
Expand All @@ -46,12 +32,12 @@ export async function listAppInternal(options: OptionsBase, silent = false) {

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)

await verifyUser(supabase, options.apikey, ['write', 'all', 'read', 'upload'])
await resolveUserIdFromApiKey(supabase, options.apikey)

if (!silent)
log.info('Getting active bundle in Capgo')

const allApps = await getActiveApps(supabase, silent)
const allApps = await getActiveApps(supabase, options.apikey)

if (!allApps.length) {
if (!silent)
Expand Down
10 changes: 2 additions & 8 deletions src/app/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import {
getAppId,
getConfig,
getContentType,
getOrganization,
getOrganizationId,
OrganizationPerm,
sendEvent,
verifyUser,
} from '../utils'

export async function setAppInternal(appId: string, options: Options, silent = false) {
Expand All @@ -37,12 +36,8 @@ export async function setAppInternal(appId: string, options: Options, silent = f
}

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
const organization = await getOrganization(supabase, ['admin', 'super_admin'])
const organizationUid = organization.gid

const userId = await verifyUser(supabase, options.apikey, ['write', 'all'])

await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin, silent)
const organizationUid = await getOrganizationId(supabase, appId)

const { name, icon, retention, exposeMetadata } = options

Expand Down Expand Up @@ -111,7 +106,6 @@ export async function setAppInternal(appId: string, options: Options, silent = f
expose_metadata: exposeMetadata,
})
.eq('app_id', appId)
.eq('user_id', userId)

if (dbError) {
if (!silent)
Expand Down
8 changes: 6 additions & 2 deletions src/build/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { WebSocket as PartySocket } from 'partysocket'
import * as tus from 'tus-js-client'
import WS from 'ws' // TODO: remove when min version nodejs 22 is bump, should do it in july 2026 as it become deprecated
import pack from '../../package.json'
import { createSupabaseClient, findSavedKey, getConfig, getOrganizationId, sendEvent, verifyUser } from '../utils'
import { assertCliPermission, createSupabaseClient, findSavedKey, getConfig, getOrganizationId, resolveUserIdFromApiKey, sendEvent } from '../utils'
import { mergeCredentials, MIN_OUTPUT_RETENTION_SECONDS, parseOptionalBoolean, parseOutputRetentionSeconds } from './credentials'
import { buildProvisioningMap } from './credentials-command'
import { getPlatformDirFromCapacitorConfig } from './platform-paths'
Expand Down Expand Up @@ -1034,7 +1034,11 @@ export async function requestBuildInternal(appId: string, options: BuildRequestO
const host = options.supaHost || 'https://api.capgo.app'

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await verifyUser(supabase, options.apikey, ['write', 'all'])
await resolveUserIdFromApiKey(supabase, options.apikey)
await assertCliPermission(supabase, options.apikey, 'app.build_native', { appId }, {
message: `Insufficient permissions to request a native build for app ${appId}`,
silent,
})

// Get organization ID for analytics
const orgId = await getOrganizationId(supabase, appId)
Expand Down
4 changes: 2 additions & 2 deletions src/bundle/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
getConfig,
getHumanDate,
OrganizationPerm,
verifyUser,
resolveUserIdFromApiKey,
} from '../utils'

async function removeVersions(
Expand Down Expand Up @@ -80,7 +80,7 @@ export async function cleanupBundleInternal(appId: string, options: BundleCleanu

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await check2FAComplianceForApp(supabase, appId, silent)
await verifyUser(supabase, options.apikey, ['write', 'all'])
await resolveUserIdFromApiKey(supabase, options.apikey)
await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.write, silent, true)

if (!silent)
Expand Down
4 changes: 2 additions & 2 deletions src/bundle/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getConfig,
isCompatible,
OrganizationPerm,
verifyUser,
resolveUserIdFromApiKey,
} from '../utils'

interface CompatibilityResult {
Expand Down Expand Up @@ -64,7 +64,7 @@ export async function checkCompatibilityInternal(
enrichedOptions.supaAnon,
)
await check2FAComplianceForApp(supabase, resolvedAppId, silent)
await verifyUser(supabase, enrichedOptions.apikey, ['write', 'all', 'read', 'upload'])
await resolveUserIdFromApiKey(supabase, enrichedOptions.apikey)
await checkAppExistsAndHasPermissionOrgErr(
supabase,
enrichedOptions.apikey,
Expand Down
4 changes: 2 additions & 2 deletions src/bundle/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { BundleDeleteOptions } from '../schemas/bundle'
import { intro, log, outro } from '@clack/prompts'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { deleteSpecificVersion } from '../api/versions'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, getOrganizationId, OrganizationPerm, sendEvent, verifyUser } from '../utils'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, getOrganizationId, OrganizationPerm, resolveUserIdFromApiKey, sendEvent } from '../utils'

export async function deleteBundleInternal(bundleId: string, appId: string, options: BundleDeleteOptions, silent = false) {
if (!silent)
Expand Down Expand Up @@ -32,7 +32,7 @@ export async function deleteBundleInternal(bundleId: string, appId: string, opti

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await check2FAComplianceForApp(supabase, appId, silent)
await verifyUser(supabase, options.apikey, ['write', 'all'])
await resolveUserIdFromApiKey(supabase, options.apikey)
await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.write, silent, true)

if (!silent) {
Expand Down
4 changes: 2 additions & 2 deletions src/bundle/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { intro, log, outro } from '@clack/prompts'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { checkAlerts } from '../api/update'
import { displayBundles, getActiveAppVersions } from '../api/versions'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, OrganizationPerm, verifyUser } from '../utils'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, OrganizationPerm, resolveUserIdFromApiKey } from '../utils'

export async function listBundle(appId: string, options: OptionsBase, silent = false) {
if (!silent)
Expand All @@ -28,7 +28,7 @@ export async function listBundle(appId: string, options: OptionsBase, silent = f

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await check2FAComplianceForApp(supabase, appId, silent)
await verifyUser(supabase, options.apikey, ['write', 'all', 'read', 'upload'])
await resolveUserIdFromApiKey(supabase, options.apikey)
await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.read, silent, true)

if (!silent)
Expand Down
4 changes: 2 additions & 2 deletions src/bundle/unlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
getConfig,
getOrganizationId,
OrganizationPerm,
resolveUserIdFromApiKey,
sendEvent,
verifyUser,
} from '../utils'

export async function unlinkDeviceInternal(
Expand Down Expand Up @@ -69,7 +69,7 @@ export async function unlinkDeviceInternal(
await check2FAComplianceForApp(supabase, resolvedAppId, silent)

const [userId, orgId] = await Promise.all([
verifyUser(supabase, enrichedOptions.apikey, ['all', 'write']),
resolveUserIdFromApiKey(supabase, enrichedOptions.apikey),
getOrganizationId(supabase, resolvedAppId),
])

Expand Down
27 changes: 22 additions & 5 deletions src/bundle/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { checkAlerts } from '../api/update'
import { getChecksum } from '../checksum'
import { getRepoStarStatus, isRepoStarredInSession, starRepository } from '../github'
import { showReplicationProgress } from '../replicationProgress'
import { baseKeyV2, BROTLI_MIN_UPDATER_VERSION_V5, BROTLI_MIN_UPDATER_VERSION_V6, BROTLI_MIN_UPDATER_VERSION_V7, checkChecksum, checkCompatibilityCloud, checkPlanValidUpload, checkRemoteCliMessages, createSupabaseClient, deletedFailedVersion, findRoot, findSavedKey, formatError, getAppId, getBundleVersion, getCompatibilityDetails, getConfig, getInstalledVersion, getLocalConfig, getLocalDependencies, getOrganizationId, getPMAndCommand, getRemoteFileConfig, hasOrganizationPerm, isCompatible, isDeprecatedPluginVersion, OrganizationPerm, regexSemver, sendEvent, updateConfigUpdater, updateOrCreateChannel, updateOrCreateVersion, UPLOAD_TIMEOUT, uploadTUS, uploadUrl, verifyUser, zipFile } from '../utils'
import { baseKeyV2, BROTLI_MIN_UPDATER_VERSION_V5, BROTLI_MIN_UPDATER_VERSION_V6, BROTLI_MIN_UPDATER_VERSION_V7, checkChecksum, checkCompatibilityCloud, checkPlanValidUpload, checkRemoteCliMessages, createSupabaseClient, deletedFailedVersion, findRoot, findSavedKey, formatError, getAppId, getBundleVersion, getCompatibilityDetails, getConfig, getInstalledVersion, getLocalConfig, getLocalDependencies, getOrganizationId, getPMAndCommand, getRemoteFileConfig, hasCliPermission, hasOrganizationPerm, isCompatible, isDeprecatedPluginVersion, OrganizationPerm, regexSemver, resolveUserIdFromApiKey, sendEvent, updateConfigUpdater, updateOrCreateChannel, updateOrCreateVersion, UPLOAD_TIMEOUT, uploadTUS, uploadUrl, zipFile } from '../utils'
import { getVersionSuggestions, interactiveVersionBump } from '../versionHelpers'
import { checkIndexPosition, searchInDirectory } from './check'
import { prepareBundlePartialFiles, uploadPartial } from './partial'
Expand Down Expand Up @@ -645,9 +645,26 @@ async function setVersionInChannel(
if (!versionId)
uploadFail('Cannot get version id, cannot set channel')

const { data: apiAccess } = await supabase
.rpc('is_allowed_capgkey', { apikey, keymode: ['write', 'all'] })
.single()
const { data: existingChannel, error: channelLookupError } = await supabase
.from('channels')
.select('id')
.eq('app_id', appid)
.eq('name', channel)
.maybeSingle()

if (channelLookupError)
uploadFail(`Cannot look up channel ${channel} ${formatError(channelLookupError)}`)

const apiAccess = existingChannel?.id
? await hasCliPermission(supabase, apikey, 'channel.update_settings', {
orgId,
appId: appid,
channelId: existingChannel.id,
})
: await hasCliPermission(supabase, apikey, 'app.create_channel', {
orgId,
appId: appid,
})

if (apiAccess) {
const { error: dbError3, data } = await updateOrCreateChannel(supabase, {
Expand Down Expand Up @@ -798,7 +815,7 @@ export async function uploadBundleInternal(preAppid: string, options: OptionsUpl
// Check 2FA compliance early to give a clear error message
await check2FAComplianceForApp(supabase, appid, silent)

const userId = await verifyUser(supabase, apikey, ['write', 'all', 'upload'])
const userId = await resolveUserIdFromApiKey(supabase, apikey)
if (options.verbose)
log.info(`[Verbose] User verified successfully, user_id: ${userId}`)

Expand Down
6 changes: 3 additions & 3 deletions src/channel/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
getConfig,
getOrganizationId,
OrganizationPerm,
resolveUserIdFromApiKey,
sendEvent,
verifyUser,
} from '../utils'

export async function addChannelInternal(channelId: string, appId: string, options: ChannelAddOptions, silent = false) {
Expand All @@ -36,7 +36,7 @@ export async function addChannelInternal(channelId: string, appId: string, optio

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await check2FAComplianceForApp(supabase, appId, silent)
await verifyUser(supabase, options.apikey, ['write', 'all'])
await resolveUserIdFromApiKey(supabase, options.apikey)
await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin, silent, true)

if (!silent)
Expand All @@ -50,7 +50,7 @@ export async function addChannelInternal(channelId: string, appId: string, optio
}

const orgId = await getOrganizationId(supabase, appId)
const userId = await verifyUser(supabase, options.apikey, ['write', 'all'])
const userId = await resolveUserIdFromApiKey(supabase, options.apikey)

const res = await createChannel(supabase, {
name: channelId,
Expand Down
4 changes: 2 additions & 2 deletions src/channel/currentBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getAppId,
getConfig,
OrganizationPerm,
verifyUser,
resolveUserIdFromApiKey,
} from '../utils'

interface Channel {
Expand Down Expand Up @@ -40,7 +40,7 @@ export async function currentBundleInternal(channel: string, appId: string, opti

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await check2FAComplianceForApp(supabase, appId, silent)
await verifyUser(supabase, options.apikey, ['write', 'all', 'read'])
await resolveUserIdFromApiKey(supabase, options.apikey)
await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.read, silent, true)

if (!channel) {
Expand Down
4 changes: 2 additions & 2 deletions src/channel/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
getConfig,
getOrganizationId,
OrganizationPerm,
resolveUserIdFromApiKey,
sendEvent,
verifyUser,
} from '../utils'

export async function deleteChannelInternal(channelId: string, appId: string, options: ChannelDeleteOptions, silent = false) {
Expand All @@ -37,7 +37,7 @@ export async function deleteChannelInternal(channelId: string, appId: string, op

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await check2FAComplianceForApp(supabase, appId, silent)
const userId = await verifyUser(supabase, options.apikey, ['all'])
const userId = await resolveUserIdFromApiKey(supabase, options.apikey)

await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin, silent, true)

Expand Down
4 changes: 2 additions & 2 deletions src/channel/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { OptionsBase } from '../schemas/base'
import { intro, log, outro } from '@clack/prompts'
import { check2FAComplianceForApp, checkAppExistsAndHasPermissionOrgErr } from '../api/app'
import { displayChannels, getActiveChannels } from '../api/channels'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, OrganizationPerm, sendEvent, verifyUser } from '../utils'
import { createSupabaseClient, findSavedKey, getAppId, getConfig, OrganizationPerm, resolveUserIdFromApiKey, sendEvent } from '../utils'

export async function listChannelsInternal(appId: string, options: OptionsBase, silent = false) {
if (!silent)
Expand All @@ -26,7 +26,7 @@ export async function listChannelsInternal(appId: string, options: OptionsBase,

const supabase = await createSupabaseClient(options.apikey, options.supaHost, options.supaAnon)
await check2FAComplianceForApp(supabase, appId, silent)
const userId = await verifyUser(supabase, options.apikey, ['write', 'all', 'read', 'upload'])
const userId = await resolveUserIdFromApiKey(supabase, options.apikey)

await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.read, silent, true)

Expand Down
Loading
Loading