Skip to content
Closed
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,16 @@ Open `http://localhost:3000`.

- Supabase Auth and Postgres
- S3-compatible object storage, such as Cloudflare R2
- At least one supported model provider key, depending on which models you enable
- At least one backend-managed model provider key, depending on which models the platform enables
- LibreOffice for DOC/DOCX to PDF conversion

## SaaS Provider Mode

Mike is configured as a subscription platform, not a bring-your-own-key app.
Model provider keys live only in the backend environment (`GEMINI_API_KEY`,
`ANTHROPIC_API_KEY`, and future provider keys). The frontend only receives
provider availability flags and never stores or displays user model API keys.

## Checks

```bash
Expand Down
3 changes: 3 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ PORT=3001
FRONTEND_URL=http://localhost:3000
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SECRET_KEY=your-supabase-service-role-key
DOWNLOAD_SIGNING_SECRET=replace-with-at-least-32-random-bytes

R2_ENDPOINT_URL=https://your-account-id.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=your-r2-access-key
R2_SECRET_ACCESS_KEY=your-r2-secret-key
R2_BUCKET_NAME=mike

# Platform-managed model provider keys for the hosted subscription product.
# These stay on the backend; users do not provide their own model API keys.
GEMINI_API_KEY=your-gemini-key
ANTHROPIC_API_KEY=your-anthropic-key
OPENROUTER_API_KEY=your-openrouter-key
Expand Down
50 changes: 40 additions & 10 deletions backend/migrations/000_one_shot_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ create table if not exists public.user_profiles (
user_id uuid not null unique references auth.users(id) on delete cascade,
display_name text,
organisation text,
tier text not null default 'Free',
tier text not null default 'Starter',
message_credits_used integer not null default 0,
credits_reset_date timestamptz not null default (now() + interval '30 days'),
tabular_model text not null default 'gemini-3-flash-preview',
claude_api_key text,
gemini_api_key text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
Expand All @@ -30,14 +28,7 @@ create index if not exists idx_user_profiles_user
alter table public.user_profiles enable row level security;

drop policy if exists "Users can view their own profile" on public.user_profiles;
create policy "Users can view their own profile"
on public.user_profiles for select
using (auth.uid() = user_id);

drop policy if exists "Users can update their own profile" on public.user_profiles;
create policy "Users can update their own profile"
on public.user_profiles for update
using (auth.uid() = user_id);

create or replace function public.handle_new_user()
returns trigger
Expand Down Expand Up @@ -338,3 +329,42 @@ create table if not exists public.tabular_review_chat_messages (

create index if not exists tabular_review_chat_messages_chat_idx
on public.tabular_review_chat_messages(chat_id, created_at);

-- ---------------------------------------------------------------------------
-- Security posture
-- ---------------------------------------------------------------------------
-- App data is accessed through backend service-role routes. Keep RLS enabled
-- without direct anon/authenticated policies so browser clients cannot read or
-- write raw tables such as user profiles, document metadata, or API keys.

alter table public.user_profiles enable row level security;
alter table public.projects enable row level security;
alter table public.project_subfolders enable row level security;
alter table public.documents enable row level security;
alter table public.document_versions enable row level security;
alter table public.document_edits enable row level security;
alter table public.workflows enable row level security;
alter table public.hidden_workflows enable row level security;
alter table public.workflow_shares enable row level security;
alter table public.chats enable row level security;
alter table public.chat_messages enable row level security;
alter table public.tabular_reviews enable row level security;
alter table public.tabular_cells enable row level security;
alter table public.tabular_review_chats enable row level security;
alter table public.tabular_review_chat_messages enable row level security;

revoke all on public.user_profiles from anon, authenticated;
revoke all on public.projects from anon, authenticated;
revoke all on public.project_subfolders from anon, authenticated;
revoke all on public.documents from anon, authenticated;
revoke all on public.document_versions from anon, authenticated;
revoke all on public.document_edits from anon, authenticated;
revoke all on public.workflows from anon, authenticated;
revoke all on public.hidden_workflows from anon, authenticated;
revoke all on public.workflow_shares from anon, authenticated;
revoke all on public.chats from anon, authenticated;
revoke all on public.chat_messages from anon, authenticated;
revoke all on public.tabular_reviews from anon, authenticated;
revoke all on public.tabular_cells from anon, authenticated;
revoke all on public.tabular_review_chats from anon, authenticated;
revoke all on public.tabular_review_chat_messages from anon, authenticated;
61 changes: 61 additions & 0 deletions backend/migrations/001_security_lockdown.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
-- Lock app data behind backend service-role APIs and clean up tabular cells
-- that point at documents outside their review authorization boundary.

delete from public.tabular_cells c
where not exists (
select 1
from public.documents d
where d.id = c.document_id
)
or exists (
select 1
from public.tabular_reviews r
left join public.documents d on d.id = c.document_id
where r.id = c.review_id
and (
d.id is null
or (
r.project_id is not null
and d.project_id is distinct from r.project_id
)
or (
r.project_id is null
and d.user_id is distinct from r.user_id
)
)
);

alter table public.user_profiles enable row level security;
alter table public.projects enable row level security;
alter table public.project_subfolders enable row level security;
alter table public.documents enable row level security;
alter table public.document_versions enable row level security;
alter table public.document_edits enable row level security;
alter table public.workflows enable row level security;
alter table public.hidden_workflows enable row level security;
alter table public.workflow_shares enable row level security;
alter table public.chats enable row level security;
alter table public.chat_messages enable row level security;
alter table public.tabular_reviews enable row level security;
alter table public.tabular_cells enable row level security;
alter table public.tabular_review_chats enable row level security;
alter table public.tabular_review_chat_messages enable row level security;

drop policy if exists "Users can view their own profile" on public.user_profiles;
drop policy if exists "Users can update their own profile" on public.user_profiles;

revoke all on public.user_profiles from anon, authenticated;
revoke all on public.projects from anon, authenticated;
revoke all on public.project_subfolders from anon, authenticated;
revoke all on public.documents from anon, authenticated;
revoke all on public.document_versions from anon, authenticated;
revoke all on public.document_edits from anon, authenticated;
revoke all on public.workflows from anon, authenticated;
revoke all on public.hidden_workflows from anon, authenticated;
revoke all on public.workflow_shares from anon, authenticated;
revoke all on public.chats from anon, authenticated;
revoke all on public.chat_messages from anon, authenticated;
revoke all on public.tabular_reviews from anon, authenticated;
revoke all on public.tabular_cells from anon, authenticated;
revoke all on public.tabular_review_chats from anon, authenticated;
revoke all on public.tabular_review_chat_messages from anon, authenticated;
Loading