this repo has no description
1package db
2
3import (
4 "context"
5 "database/sql"
6 "encoding/hex"
7 "fmt"
8 "log"
9
10 "github.com/google/uuid"
11 _ "github.com/mattn/go-sqlite3"
12 "golang.org/x/crypto/bcrypt"
13)
14
15type DB struct {
16 db *sql.DB
17}
18
19func Make(dbPath string) (*DB, error) {
20 db, err := sql.Open("sqlite3", dbPath)
21 if err != nil {
22 return nil, err
23 }
24 _, err = db.Exec(`
25 create table if not exists registrations (
26 id integer primary key autoincrement,
27 domain text not null unique,
28 did text not null,
29 secret text not null,
30 created integer default (strftime('%s', 'now')),
31 registered integer
32 );
33 `)
34 if err != nil {
35 return nil, err
36 }
37 return &DB{db: db}, nil
38}
39
40type RegStatus uint32
41
42const (
43 Registered RegStatus = iota
44 Unregistered
45 Pending
46)
47
48// returns registered status, did of owner, error
49func (d *DB) RegistrationStatus(domain string) (RegStatus, string, error) {
50 var registeredBy string
51 var registratedAt *uint64
52 err := d.db.QueryRow(`
53 select did, registered from registrations
54 where domain = ?
55 `, domain).Scan(®isteredBy, ®istratedAt)
56 if err != nil {
57 if err == sql.ErrNoRows {
58 return Unregistered, "", nil
59 } else {
60 return Unregistered, "", err
61 }
62 }
63
64 if registratedAt != nil {
65 return Registered, registeredBy, nil
66 } else {
67 return Pending, registeredBy, nil
68 }
69}
70
71func (d *DB) GenerateRegistrationKey(domain, did string) (string, error) {
72 // sanity check: does this domain already have a registration?
73 status, owner, err := d.RegistrationStatus(domain)
74 if err != nil {
75 return "", err
76 }
77 switch status {
78 case Registered:
79 // already registered by `owner`
80 return "", fmt.Errorf("%s already registered by %s", domain, owner)
81 case Pending:
82 log.Printf("%s registered by %s, status pending", domain, owner)
83 // TODO: provide a warning here, and allow the current user to overwrite
84 // the registration, this prevents users from registering domains that they
85 // do not own
86 default:
87 // ok, we can register this domain
88 }
89
90 secret := uuid.New().String()
91 hashedSecret, err := bcrypt.GenerateFromPassword([]byte(secret), 3)
92
93 if err != nil {
94 return "", err
95 }
96
97 _, err = d.db.Exec(`
98 insert into registrations (domain, did, secret)
99 values (?, ?, ?)
100 on conflict(domain) do update set did = excluded.did, secret = excluded.secret
101 `, domain, did, fmt.Sprintf("%x", hashedSecret))
102
103 if err != nil {
104 return "", err
105 }
106
107 return secret, nil
108}
109
110func (d *DB) Register(domain, secret string) error {
111 ctx := context.TODO()
112
113 tx, err := d.db.BeginTx(ctx, nil)
114 if err != nil {
115 return err
116 }
117
118 res := tx.QueryRow(`select secret from registrations where domain = ?`, domain)
119
120 var hexSecret string
121 err = res.Scan(&hexSecret)
122 if err != nil {
123 return err
124 }
125
126 decoded, err := hex.DecodeString(hexSecret)
127 if err != nil {
128 return err
129 }
130
131 err = bcrypt.CompareHashAndPassword(decoded, []byte(secret))
132 if err != nil {
133 return err
134 }
135
136 _, err = tx.Exec(`
137 update registrations
138 set registered = strftime('%s', 'now')
139 where domain = ?;
140 `, domain)
141 if err != nil {
142 return err
143 }
144
145 err = tx.Commit()
146 if err != nil {
147 return err
148 }
149
150 return nil
151}