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