A internal/api/update.go => internal/api/update.go +59 -0
@@ 0,0 1,59 @@
+/*
+ * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
+ * more information.
+ */
+package api
+
+import (
+ "net/http"
+
+ "git.src.quest/~skye/felu-ddns/internal/db"
+ "github.com/gin-gonic/gin"
+)
+
+func UpdateA() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ domain := c.Query("domain")
+ if domain == "" {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "status": "error",
+ "error": "no domain was provided",
+ })
+ c.Abort()
+ return
+ }
+
+ apiKey := c.Query("api_key")
+ if apiKey == "" {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "status": "error",
+ "error": "no api key was provided",
+ })
+ c.Abort()
+ return
+ }
+
+ aRecord := c.Query("record")
+ if aRecord == "" {
+ aRecord = c.ClientIP()
+ }
+
+ err := db.UpdateDomainARecord(domain, apiKey, aRecord)
+ if err != nil {
+ // FIXME: Handle better, "bad api key" is just the most likely scenario
+ c.JSON(http.StatusBadRequest, gin.H{
+ "status": "error",
+ "error": "bad api key",
+ })
+ c.Abort()
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "status": "success",
+ "a_record": aRecord,
+ })
+ }
+}
M internal/db/domains.go => internal/db/domains.go +25 -2
@@ 7,7 7,9 @@
package db
import (
- "git.src.quest/~skye/felu-ddns/internal/api"
+ "errors"
+
+ "git.src.quest/~skye/felu-ddns/internal/util"
"github.com/oklog/ulid/v2"
)
@@ 45,7 47,7 @@ func FetchDomainsForUser(userId string) ([]Domain, error) {
func CreateDomain(domain string, aRecord string, owner string) error {
ulid := ulid.Make().String()
- apikey := api.GenKey()
+ apikey := util.GenApiKey()
_, err := DBConn.Exec(`INSERT INTO domains(id, apikey, ddns_domain, a_record, owner)
VALUES ($1, $2, $3, $4, $5)`, ulid, apikey, domain, aRecord, owner)
if err != nil {
@@ 80,3 82,24 @@ func FetchDomainARecord(ddns_domain string) (string, error) {
}
return aRecord, nil
}
+
+func UpdateDomainARecord(ddns_domain string, providedApiKey string, aRecord string) error {
+ var domainApiKey string
+ err := DBConn.QueryRow(`SELECT apikey FROM domains WHERE ddns_domain = $1`,
+ ddns_domain).Scan(&domainApiKey)
+ if err != nil {
+ return err
+ }
+
+ if domainApiKey != providedApiKey {
+ return errors.New("API key doesn't match")
+ }
+
+ _, err = DBConn.Exec(`UPDATE domains SET a_record = $1 WHERE ddns_domain = $2`,
+ aRecord, ddns_domain)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
M internal/routers/api.go => internal/routers/api.go +13 -3
@@ 9,6 9,7 @@ package routers
import (
"net/http"
+ "git.src.quest/~skye/felu-ddns/internal/api"
"git.src.quest/~skye/felu-ddns/internal/log"
"github.com/gin-gonic/gin"
)
@@ 16,9 17,16 @@ import (
func SetupAPIRouter(version string) *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
- r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
- return log.GinFormat(param, "api")
- }))
+
+ var logger = gin.LoggerWithConfig(gin.LoggerConfig{
+ Formatter: func(param gin.LogFormatterParams) string {
+ return log.GinFormat(param, "api")
+ },
+ // NOTE: Don't log paths with query params, also update paths are used _a lot_
+ SkipPaths: []string{"/update/a"},
+ })
+
+ r.Use(logger)
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
@@ 32,5 40,7 @@ func SetupAPIRouter(version string) *gin.Engine {
})
})
+ r.GET("/update/a", api.UpdateA())
+
return r
}
R internal/api/key.go => internal/util/apikey.go +2 -2
@@ 4,13 4,13 @@
* This file is licensed under AGPL-3.0-or-later, see NOTICE and LICENSE for
* more information.
*/
-package api
+package util
import "math/rand"
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
-func GenKey() string {
+func GenApiKey() string {
// NOTE: "Good enough"
b := make([]byte, 48)
for i := range b {