···1515- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins
1616- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA
17171818-## Captcha on Create Account
1818+## Captcha on account creation
1919+2020+Require a `verificationCode` set on the `createAccount` request. This is gotten from completing a captcha challenge
2121+hosted on the
2222+PDS mimicking what the Bluesky Entryway does. Migration tools will need to support this, but social-apps will support
2323+and redirect to `GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT`. This is how the clients know to get the code to prove a captcha
2424+was successful.
2525+2626+- Requires `GATEKEEPER_CREATE_ACCOUNT_CAPTCHA` to be set to true.
2727+- Requires `PDS_HCAPTCHA_SITE_KEY` and `PDS_HCAPTCHA_SECRET_KEY` to be set. Can sign up at https://www.hcaptcha.com/
2828+- Requires proxying `/xrpc/com.atproto.server.describeServer`, `/xrpc/com.atproto.server.createAccount` and `/gate/*` to
2929+ PDS
3030+ Gatekeeper
3131+- Optional `GATEKEEPER_JWE_KEY` key to encrypt the captcha verification code. Defaults to a random 32 byte key. Not
3232+ strictly needed unless you're scaling
3333+- Optional`GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT` default redirect on captcha success. Defaults to `https://bsky.app`.
3434+- Optional `GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS` allowed redirect urls for captcha success. You want these to match the
3535+ url showing the captcha. Defaults are:
3636+ - https://bsky.app
3737+ - https://pdsmoover.com
3838+ - https://blacksky.community
3939+ - https://tektite.cc
4040+4141+## Block account creation unless it's a migration
19422020-Future feature?
4343+You can set `GATEKEEPER_ALLOW_ONLY_MIGRATIONS` to block createAccount unless it's via a migration. This does not require
4444+a change for migration tools, but social-apps create a new account will no longer work and to create a brand new account
4545+users will need to do this via the Oauth account create screen on the PDS. We recommend setting `PDS_HCAPTCHA_SITE_KEY`
4646+and `PDS_HCAPTCHA_SECRET_KEY` so the OAuth screen is protected by a captcha if you use this with invite codes turned
4747+off.
21482249# Setup
2350···4976 - pds
5077```
51785252-For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml).
7979+For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up
8080+correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml).
53815482```yml
5583gatekeeper:
5656- container_name: gatekeeper
5757- image: 'fatfingers23/pds_gatekeeper:latest'
5858- restart: unless-stopped
5959- volumes:
6060- - '/pds:/pds'
6161- environment:
6262- - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}'
6363- - 'PDS_BASE_URL=http://pds:3000'
6464- - GATEKEEPER_HOST=0.0.0.0
6565- depends_on:
6666- - pds
6767- healthcheck:
6868- test:
6969- - CMD
7070- - timeout
7171- - '1'
7272- - bash
7373- - '-c'
7474- - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080'
7575- interval: 10s
7676- timeout: 5s
7777- retries: 3
7878- start_period: 10s
7979- labels:
8080- - traefik.enable=true
8181- - 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))'
8282- - traefik.http.routers.pds-gatekeeper.entrypoints=https
8383- - traefik.http.routers.pds-gatekeeper.tls=true
8484- - traefik.http.routers.pds-gatekeeper.priority=100
8585- - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors
8686- - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080
8787- - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http
8888- - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH'
8989- - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*'
9090- - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*'
9191- - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100
9292- - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true
9393- - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
8484+ container_name: gatekeeper
8585+ image: 'fatfingers23/pds_gatekeeper:latest'
8686+ restart: unless-stopped
8787+ volumes:
8888+ - '/pds:/pds'
8989+ environment:
9090+ - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}'
9191+ - 'PDS_BASE_URL=http://pds:3000'
9292+ - GATEKEEPER_HOST=0.0.0.0
9393+ depends_on:
9494+ - pds
9595+ healthcheck:
9696+ test:
9797+ - CMD
9898+ - timeout
9999+ - '1'
100100+ - bash
101101+ - '-c'
102102+ - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080'
103103+ interval: 10s
104104+ timeout: 5s
105105+ retries: 3
106106+ start_period: 10s
107107+ labels:
108108+ - traefik.enable=true
109109+ - 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))'
110110+ - traefik.http.routers.pds-gatekeeper.entrypoints=https
111111+ - traefik.http.routers.pds-gatekeeper.tls=true
112112+ - traefik.http.routers.pds-gatekeeper.priority=100
113113+ - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors
114114+ - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080
115115+ - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http
116116+ - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH'
117117+ - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*'
118118+ - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*'
119119+ - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100
120120+ - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true
121121+ - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
94122```
9512396124## Caddy setup
···99127in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile).
100128This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS.
101129102102-```caddyfile
130130+```
103131 @gatekeeper {
104104- path /xrpc/com.atproto.server.getSession
105105- path /xrpc/com.atproto.server.updateEmail
106106- path /xrpc/com.atproto.server.createSession
107107- path /xrpc/com.atproto.server.createAccount
108108- path /@atproto/oauth-provider/~api/sign-in
132132+ path /xrpc/com.atproto.server.getSession
133133+ path /xrpc/com.atproto.server.describeServer
134134+ path /xrpc/com.atproto.server.updateEmail
135135+ path /xrpc/com.atproto.server.createSession
136136+ path /xrpc/com.atproto.server.createAccount
137137+ path /@atproto/oauth-provider/~api/sign-in
138138+ path /gate/*
109139 }
110140111141 handle @gatekeeper {
112112- reverse_proxy http://localhost:8080
113113- }
142142+ reverse_proxy http://localhost:8080
143143+ }
114144115115- reverse_proxy http://localhost:3000
145145+ reverse_proxy http://localhost:3000
116146```
117147118148If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to
119149`localhost:8081` (or w/e port you want).
120150121121-```caddyfile
151151+```
122152http://*.localhost:8082, http://localhost:8082 {
123123- @gatekeeper {
124124- path /xrpc/com.atproto.server.getSession
125125- path /xrpc/com.atproto.server.updateEmail
126126- path /xrpc/com.atproto.server.createSession
127127- path /xrpc/com.atproto.server.createAccount
128128- path /@atproto/oauth-provider/~api/sign-in
129129- }
153153+ @gatekeeper {
154154+ path /xrpc/com.atproto.server.getSession
155155+ path /xrpc/com.atproto.server.describeServer
156156+ path /xrpc/com.atproto.server.updateEmail
157157+ path /xrpc/com.atproto.server.createSession
158158+ path /xrpc/com.atproto.server.createAccount
159159+ path /@atproto/oauth-provider/~api/sign-in
160160+ path /gate/*
161161+ }
130162131131- handle @gatekeeper {
132132- reverse_proxy http://localhost:8080 {
133133- #Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper
134134- header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
135135- }
136136- }
137137-138138- reverse_proxy http://localhost:3000
163163+ handle @gatekeeper {
164164+ #This is the address for PDS gatekeeper, default is 8080
165165+ reverse_proxy http://localhost:8080
166166+ #Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper
167167+ header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
168168+ }
169169+ reverse_proxy http://localhost:3000
139170}
140171141172```
···168199169200`GATEKEEPER_CREATE_ACCOUNT_BURST` - Sets how many requests can be made in a burst. In the prior example this is where
170201the 5 comes from. Example can set this to 10 to allow for 10 requests in a burst, and after 60 seconds it will drop one
171171-off. 202202+off.
203203+204204+`GATEKEEPER_ALLOW_ONLY_MIGRATIONS` - Defaults false. If set to true, will only allow the
205205+`/xrpc/com.atproto.server.createAccount` endpoint to be used for migrations. Meaning it will check for the serviceAuth
206206+token and verify it is valid.
207207+
+22-22
examples/Caddyfile
···11{
22- email youremail@myemail.com
33- on_demand_tls {
44- ask http://localhost:3000/tls-check
55- }
22+ email youremail@myemail.com
33+ on_demand_tls {
44+ ask http://localhost:3000/tls-check
55+ }
66}
7788*.yourpds.com, yourpds.com {
99- tls {
1010- on_demand
1111- }
1212- # You'll most likely just want from here to....
1313- @gatekeeper {
1414- path /xrpc/com.atproto.server.getSession
1515- path /xrpc/com.atproto.server.updateEmail
1616- path /xrpc/com.atproto.server.createSession
1717- path /xrpc/com.atproto.server.createAccount
1818- path /@atproto/oauth-provider/~api/sign-in
99+ tls {
1010+ on_demand
1911 }
1212+# You'll most likely just want from here to....
1313+ @gatekeeper {
1414+ path /xrpc/com.atproto.server.getSession
1515+ path /xrpc/com.atproto.server.describeServer
1616+ path /xrpc/com.atproto.server.updateEmail
1717+ path /xrpc/com.atproto.server.createSession
1818+ path /xrpc/com.atproto.server.createAccount
1919+ path /@atproto/oauth-provider/~api/sign-in
2020+ path /gate/*
2121+ }
20222121- handle @gatekeeper {
2222- #This is the address for PDS gatekeeper, default is 8080
2323- reverse_proxy http://localhost:8080
2424- }
2323+ handle @gatekeeper {
2424+ #This is the address for PDS gatekeeper, default is 8080
2525+ reverse_proxy http://localhost:8080
2626+ }
25272626- reverse_proxy http://localhost:3000
2727- #..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
2828+ reverse_proxy http://localhost:3000
2929+#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
2830}
2929-3030-
···11+-- Add migration script here
22+CREATE TABLE IF NOT EXISTS gate_codes
33+(
44+ code VARCHAR PRIMARY KEY,
55+ handle VARCHAR NOT NULL,
66+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
77+);
88+99+-- Index on created_at for efficient cleanup of expired codes
1010+CREATE INDEX IF NOT EXISTS idx_gate_codes_created_at ON gate_codes(created_at);