diff --git a/internal/db/admin.go b/internal/db/admin.go
index 1f24c7932..d0da54e31 100644
--- a/internal/db/admin.go
+++ b/internal/db/admin.go
@@ -68,6 +68,10 @@ type Admin interface {
// the number of pending sign-ups sitting in the backlog.
CountUnhandledSignups(ctx context.Context) (int, error)
+ // GetOrCreateVAPIDKeyPair creates and stores a VAPID key pair,
+ // or retrieves the existing VAPID key pair.
+ GetOrCreateVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error)
+
/*
ACTION FUNCS
*/
diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go
index ff398fca5..dacb2cb1f 100644
--- a/internal/db/bundb/admin.go
+++ b/internal/db/bundb/admin.go
@@ -27,6 +27,7 @@
"strings"
"time"
+ webpushgo "github.com/SherClockHolmes/webpush-go"
"github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/config"
@@ -442,6 +443,38 @@ func (a *adminDB) CountUnhandledSignups(ctx context.Context) (int, error) {
Count(ctx)
}
+func (a *adminDB) GetOrCreateVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error) {
+ var err error
+ var vapidKeyPair *gtsmodel.VAPIDKeyPair
+
+ // Look for previously generated keys.
+ if err = a.db.NewSelect().
+ Model(vapidKeyPair).
+ Limit(1).
+ Scan(ctx); // nocollapse
+ err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return nil, gtserror.Newf("DB error getting VAPID key pair: %w", err)
+ }
+
+ if vapidKeyPair == nil {
+ // Generate new keys.
+ vapidKeyPair = >smodel.VAPIDKeyPair{}
+ if vapidKeyPair.Private, vapidKeyPair.Public, err = webpushgo.GenerateVAPIDKeys(); err != nil {
+ return nil, gtserror.Newf("error generating VAPID key pair: %w", err)
+ }
+
+ // Save them to the database.
+ if _, err = a.db.NewInsert().
+ Model(vapidKeyPair).
+ Exec(ctx); // nocollapse
+ err != nil {
+ return nil, gtserror.Newf("DB error saving VAPID key pair: %w", err)
+ }
+ }
+
+ return vapidKeyPair, err
+}
+
/*
ACTION FUNCS
*/
diff --git a/internal/db/bundb/migrations/20241124012635_add_vapid_key_pairs.go b/internal/db/bundb/migrations/20241124012635_add_vapid_key_pairs.go
new file mode 100644
index 000000000..c1a32f6be
--- /dev/null
+++ b/internal/db/bundb/migrations/20241124012635_add_vapid_key_pairs.go
@@ -0,0 +1,51 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package migrations
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ up := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ if _, err := tx.
+ NewCreateTable().
+ Model(>smodel.VAPIDKeyPair{}).
+ IfNotExists().
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ return nil
+ })
+ }
+
+ down := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ return nil
+ })
+ }
+
+ if err := Migrations.Register(up, down); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/gtsmodel/vapidkeypair.go b/internal/gtsmodel/vapidkeypair.go
new file mode 100644
index 000000000..85883df45
--- /dev/null
+++ b/internal/gtsmodel/vapidkeypair.go
@@ -0,0 +1,28 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package gtsmodel
+
+// VAPIDKeyPair represents the instance's VAPID keys (stored as Base64 strings).
+// This table should only ever have one entry, with a known ID of 0.
+//
+// See: https://datatracker.ietf.org/doc/html/rfc8292
+type VAPIDKeyPair struct {
+ ID int `bun:"pk,notnull"`
+ Public string `bun:"notnull,nullzero"`
+ Private string `bun:"notnull,nullzero"`
+}