···7979- `base_url`: the public HTTPS origin for the PDS
8080- `hostname`: the host relays should crawl
8181- `service_handle_domain`: the suffix used for local handles
8282+- `jwt_secret`: required; the server now refuses to start if it is missing or still set to the old `perlsky-dev-secret` fallback
8283- If you want users like `alice.pds.example.com`, set `service_handle_domain` to `pds.example.com`, not `example.com`.
8384- Public handle resolution for `alice.pds.example.com` also requires wildcard DNS for `*.pds.example.com` and a reverse proxy/TLS setup that will answer those subdomains.
8485- `invite_code_required`: if true, `createAccount` requires a valid invite code
+12-1
lib/ATProto/PDS.pm
···2323use ATProto::PDS::ServiceProxy;
2424use ATProto::PDS::Store::SQLite;
2525use ATProto::PDS::XRPC::Dispatcher;
2626+use Carp qw(croak);
2627use File::Spec;
27282829has project_root => '';
···3132sub startup ($self) {
3233 my $config = $self->settings;
3334 my $root = $self->project_root;
3535+ my $jwt_secret = _require_jwt_secret($config);
3436 my $public_url = Mojo::URL->new($config->{base_url} // 'http://127.0.0.1:7755');
3537 my $metrics = ATProto::PDS::Metrics->new(
3638 service => $config->{service_name} // 'perlsky',
···4244 metrics => $metrics,
4345 );
44464545- $self->secrets([$config->{jwt_secret} // 'perlsky-dev-secret']);
4747+ $self->secrets([$jwt_secret]);
4648 $self->hook(before_dispatch => sub ($c) {
4749 return unless _cors_path($c->req->url->path);
4850···205207 routes => $routes,
206208 catalog => endpoint_catalog($root),
207209 )->register_routes;
210210+}
211211+212212+sub _require_jwt_secret ($config) {
213213+ my $jwt_secret = $config->{jwt_secret};
214214+ croak 'jwt_secret must be configured'
215215+ unless defined $jwt_secret && length $jwt_secret;
216216+ croak 'jwt_secret must not use the legacy perlsky-dev-secret default'
217217+ if $jwt_secret eq 'perlsky-dev-secret';
218218+ return $jwt_secret;
208219}
209220210221sub _cors_path ($path) {
+11-2
lib/ATProto/PDS/API/Server.pm
···621621 unless $auth =~ /\ABearer\s+(.+)\z/i;
622622 my $token = $1;
623623624624- my $decoded = eval { decode_jwt($token, $c->config_value('jwt_secret', 'perlsky-dev-secret')) };
624624+ my $decoded = eval { decode_jwt($token, _jwt_secret($c)) };
625625 if (my $err = $@) {
626626 my $message = "$err";
627627 my $code = $message =~ /expired/i ? 'ExpiredToken' : 'InvalidToken';
···674674675675sub _session_response ($c, $account, $session) {
676676 my $issuer = service_did($c->app->settings);
677677- my $secret = $c->config_value('jwt_secret', 'perlsky-dev-secret');
677677+ my $secret = _jwt_secret($c);
678678 my $now = time;
679679 my $scope = _canonical_access_scope($session->{scope});
680680 my $refresh_exp = $session->{expires_at} // ($now + (30 * 24 * 60 * 60));
···709709 return TOKEN_AUD_ACCESS unless defined $scope && length $scope;
710710 return TOKEN_AUD_ACCESS if $scope eq 'atproto';
711711 return $scope;
712712+}
713713+714714+sub _jwt_secret ($c) {
715715+ my $secret = $c->config_value('jwt_secret');
716716+ xrpc_error(500, 'ServerMisconfigured', 'jwt_secret is not configured')
717717+ unless defined $secret && length $secret;
718718+ xrpc_error(500, 'ServerMisconfigured', 'jwt_secret is using the legacy dev default')
719719+ if $secret eq 'perlsky-dev-secret';
720720+ return $secret;
712721}
713722714723sub _normalize_lxm ($lxm = q()) {