Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
"account-password-heading": "Change my password",
"account-updated-succ": "Account updated successfully",
"action": "Action",
"actor": "Actor",
"actor-user-email": "User email",
"action-app-anr": "Android app not responding",
"action-app-crash": "App crash",
"action-app-crash-native": "Native app crash",
Expand Down Expand Up @@ -285,6 +287,8 @@
"api-key-create-partial-failure-title": "API key created, but role assignment failed",
"api-key-create-partial-failure-warning-hashed": "We could not finish assigning roles, and automatic cleanup failed. This secure key may still exist in a partial state. Copy it now and store it safely because you will not be able to see it again.",
"api-key-create-partial-failure-warning-plain": "We could not finish assigning roles, and automatic cleanup failed. This API key may still exist in a partial state. Copy it now and keep it safe in case you need to delete or inspect it manually.",
"api-key-id": "API key ID",
"api-key-name": "API key name",
"api-key-not-found": "API key not found",
"api-key-policy": "API Key Policy",
"api-key-policy-description": "Configure policies for API keys used with this organization.",
Expand Down Expand Up @@ -438,6 +442,7 @@
"audit-orgs-delete": "Organization Deleted",
"audit-orgs-insert": "Organization Created",
"audit-orgs-update": "Organization Updated",
"automated": "Automated",
"available-channels": "Available channels",
"available-in-the-san": "Available in the sandbox app",
"available-versions": "Available bundles",
Expand Down Expand Up @@ -1941,6 +1946,7 @@
"start-your-first-build": "Start your first native build",
"statistics": "Statistics",
"status": "Status",
"source": "Source",
"storage-chart-mode": "Storage chart mode",
"storage-hourly": "Hourly",
"storage-trend": "Storage Trend",
Expand Down
72 changes: 63 additions & 9 deletions src/components/tables/AuditLogTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { useSupabase } from '~/services/supabase'
import { useDialogV2Store } from '~/stores/dialogv2'
import { useOrganizationStore } from '~/stores/organization'

type AuditActorType = 'user' | 'apikey' | 'system' | 'unknown'

interface AuditLogRow {
id: number
created_at: string
Expand All @@ -30,6 +32,11 @@ interface AuditLogRow {
old_record: Record<string, unknown> | null
new_record: Record<string, unknown> | null
changed_fields: string[] | null
actor_type: AuditActorType
actor_user_id: string | null
actor_user_email: string | null
actor_apikey_id: number | null
actor_apikey_name: string | null
}

interface ExtendedAuditLog extends AuditLogRow {
Expand Down Expand Up @@ -127,8 +134,8 @@ const columns: Ref<TableColumn[]> = ref<TableColumn[]>([
class: 'truncate max-w-8',
},
{
label: 'email',
key: 'user_id',
label: 'actor',
key: 'actor',
mobile: false,
sortable: false,
class: 'truncate max-w-8',
Expand Down Expand Up @@ -230,6 +237,38 @@ function getTableLabel(tableName: string): string {
}
}

function getActorTypeLabel(actorType: AuditActorType): string {
switch (actorType) {
case 'user':
return t('user')
case 'apikey':
return t('api-key')
case 'system':
return t('automated')
default:
return t('unknown')
}
}

function getActorUserEmail(item: ExtendedAuditLog): string | null {
return item.actor_user_email || item.user?.email || null
}
Comment on lines +253 to +255

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict member/email fallback to user actors only.

Current fallback (actor_user_id || user_id + unconditional getActorUserEmail(...) render) can display user email for non-user actors (API key/system), which conflicts with the actor attribution model and can mislead incident/support analysis.

Proposed fix
 function getActorUserEmail(item: ExtendedAuditLog): string | null {
-  return item.actor_user_email || item.user?.email || null
+  if (item.actor_type !== 'user')
+    return null
+  return item.actor_user_email || item.user?.email || null
 }
@@
     const rows = (data ?? []) as ExtendedAuditLog[]

     for (const item of rows) {
-      const memberId = item.actor_user_id || item.user_id
+      const memberId = item.actor_type === 'user'
+        ? (item.actor_user_id || item.user_id)
+        : null
       if (memberId) {
         const member = membersMap.value.get(memberId)
         if (member) {
           item.user = member
         }
       }
     }

Also applies to: 356-357, 637-640

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/tables/AuditLogTable.vue` around lines 253 - 255, The
getActorUserEmail helper currently returns actor_user_email or user?.email
unconditionally; change getActorUserEmail(ExtendedAuditLog) so it only returns
those values when the audit item represents a user actor (e.g., item.actor_type
=== 'user'), otherwise return null; likewise update places that fallback to
actor_user_id / user_id (calls/usages of getActorUserEmail and any inline
fallbacks) to only use those user-specific fields when actor_type === 'user' to
prevent showing user emails/IDs for non-user actors (API key/system).


function getActorDisplay(item: ExtendedAuditLog): string {
if (item.actor_type === 'apikey') {
const apiKey = item.actor_apikey_id ? `${t('api-key')} #${item.actor_apikey_id}` : t('api-key')
return item.actor_apikey_name ? `${apiKey} (${item.actor_apikey_name})` : apiKey
}

if (item.actor_type === 'system')
return t('automated')

if (item.actor_type === 'user')
return getActorUserEmail(item) || item.actor_user_id || item.user_id || '-'

return t('unknown')
}

async function openDetails(item: ExtendedAuditLog) {
selectedLog.value = item
dialogStore.openDialog({
Expand All @@ -255,8 +294,8 @@ function displayValueKey(elem: ExtendedAuditLog, col: TableColumn): string {
return getTableLabel(elem.table_name)
case 'operation':
return getOperationLabel(elem.operation)
case 'user_id':
return elem.user?.email || '-'
case 'actor':
return getActorDisplay(elem)
case 'changed_fields':
return getChangedFieldsDisplay(elem)
case 'details':
Expand Down Expand Up @@ -314,8 +353,9 @@ async function fetchAuditLogs() {
const rows = (data ?? []) as ExtendedAuditLog[]

for (const item of rows) {
if (item.user_id) {
const member = membersMap.value.get(item.user_id)
const memberId = item.actor_user_id || item.user_id
if (memberId) {
const member = membersMap.value.get(memberId)
if (member) {
item.user = member
}
Expand Down Expand Up @@ -581,9 +621,23 @@ onUnmounted(() => {
</span>
</div>

<div v-if="selectedLog.user" class="text-sm text-gray-700 dark:text-gray-300">
<span class="font-medium">{{ t('email') }}:</span>
{{ selectedLog.user.email }}
<div class="space-y-1 text-sm text-gray-700 dark:text-gray-300">
<div>
<span class="font-medium">{{ t('source') }}:</span>
{{ getActorTypeLabel(selectedLog.actor_type) }}
</div>
<div v-if="selectedLog.actor_type === 'apikey' && selectedLog.actor_apikey_id">
<span class="font-medium">{{ t('api-key-id') }}:</span>
#{{ selectedLog.actor_apikey_id }}
</div>
<div v-if="selectedLog.actor_type === 'apikey' && selectedLog.actor_apikey_name">
<span class="font-medium">{{ t('api-key-name') }}:</span>
{{ selectedLog.actor_apikey_name }}
</div>
<div v-if="getActorUserEmail(selectedLog)">
<span class="font-medium">{{ t('actor-user-email') }}:</span>
{{ getActorUserEmail(selectedLog) }}
</div>
</div>

<div v-if="selectedLog.operation === 'UPDATE' && selectedLog.changed_fields?.length">
Expand Down
32 changes: 16 additions & 16 deletions src/types/supabase.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,11 @@ export type Database = {
}
audit_logs: {
Row: {
actor_apikey_id: number | null
actor_apikey_name: string | null
actor_type: string
actor_user_email: string | null
actor_user_id: string | null
changed_fields: string[] | null
created_at: string
id: number
Expand All @@ -396,6 +401,11 @@ export type Database = {
user_id: string | null
}
Insert: {
actor_apikey_id?: number | null
actor_apikey_name?: string | null
actor_type?: string
actor_user_email?: string | null
actor_user_id?: string | null
changed_fields?: string[] | null
created_at?: string
id?: number
Expand All @@ -408,6 +418,11 @@ export type Database = {
user_id?: string | null
}
Update: {
actor_apikey_id?: number | null
actor_apikey_name?: string | null
actor_type?: string
actor_user_email?: string | null
actor_user_id?: string | null
changed_fields?: string[] | null
created_at?: string
id?: number
Expand All @@ -419,22 +434,7 @@ export type Database = {
table_name?: string
user_id?: string | null
}
Relationships: [
{
foreignKeyName: "audit_logs_org_id_fkey"
columns: ["org_id"]
isOneToOne: false
referencedRelation: "orgs"
referencedColumns: ["id"]
},
{
foreignKeyName: "audit_logs_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
]
Relationships: []
}
bandwidth_usage: {
Row: {
Expand Down
5 changes: 5 additions & 0 deletions supabase/functions/_backend/public/organization/audit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ const auditLogSchema = type({
old_record: 'unknown',
new_record: 'unknown',
changed_fields: 'string[] | null',
actor_type: '"user" | "apikey" | "system" | "unknown"',
actor_user_id: 'string | null',
actor_user_email: 'string | null',
actor_apikey_id: 'number | null',
actor_apikey_name: 'string | null',
})

const auditLogsSchema = auditLogSchema.array()
Expand Down
32 changes: 16 additions & 16 deletions supabase/functions/_backend/utils/supabase.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,11 @@ export type Database = {
}
audit_logs: {
Row: {
actor_apikey_id: number | null
actor_apikey_name: string | null
actor_type: string
actor_user_email: string | null
actor_user_id: string | null
changed_fields: string[] | null
created_at: string
id: number
Expand All @@ -396,6 +401,11 @@ export type Database = {
user_id: string | null
}
Insert: {
actor_apikey_id?: number | null
actor_apikey_name?: string | null
actor_type?: string
actor_user_email?: string | null
actor_user_id?: string | null
changed_fields?: string[] | null
created_at?: string
id?: number
Expand All @@ -408,6 +418,11 @@ export type Database = {
user_id?: string | null
}
Update: {
actor_apikey_id?: number | null
actor_apikey_name?: string | null
actor_type?: string
actor_user_email?: string | null
actor_user_id?: string | null
changed_fields?: string[] | null
created_at?: string
id?: number
Expand All @@ -419,22 +434,7 @@ export type Database = {
table_name?: string
user_id?: string | null
}
Relationships: [
{
foreignKeyName: "audit_logs_org_id_fkey"
columns: ["org_id"]
isOneToOne: false
referencedRelation: "orgs"
referencedColumns: ["id"]
},
{
foreignKeyName: "audit_logs_user_id_fkey"
columns: ["user_id"]
isOneToOne: false
referencedRelation: "users"
referencedColumns: ["id"]
},
]
Relationships: []
}
bandwidth_usage: {
Row: {
Expand Down
15 changes: 15 additions & 0 deletions supabase/functions/_backend/utils/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export interface WebhookPayload {
old_record: any | null
new_record: any | null
changed_fields: string[] | null
actor_type?: 'user' | 'apikey' | 'system' | 'unknown'
actor_user_id?: string | null
actor_user_email?: string | null
actor_apikey_id?: number | null
actor_apikey_name?: string | null
}
}

Expand Down Expand Up @@ -70,6 +75,11 @@ export interface AuditLogData {
new_record: any | null
changed_fields: string[] | null
user_id: string | null
actor_type?: 'user' | 'apikey' | 'system' | 'unknown'
actor_user_id?: string | null
actor_user_email?: string | null
actor_apikey_id?: number | null
actor_apikey_name?: string | null
created_at: string
}

Expand Down Expand Up @@ -172,6 +182,11 @@ export function buildWebhookPayload(auditLogData: AuditLogData): WebhookPayload
old_record: auditLogData.old_record,
new_record: auditLogData.new_record,
changed_fields: auditLogData.changed_fields,
actor_type: auditLogData.actor_type ?? (auditLogData.user_id ? 'user' : 'unknown'),
actor_user_id: auditLogData.actor_user_id ?? auditLogData.user_id,
actor_user_email: auditLogData.actor_user_email ?? null,
actor_apikey_id: auditLogData.actor_apikey_id ?? null,
actor_apikey_name: auditLogData.actor_apikey_name ?? null,
},
}
}
Expand Down
Loading
Loading