1diff --git a/pkg/database/repository/base.go b/pkg/database/repository/base.go
2new file mode 100644
3index 0000000000000000000000000000000000000000..a9d69c990f2425f791b8e4ce2746a75452fcfb2f
4--- /dev/null
5+++ b/pkg/database/repository/base.go
6@@ -0,0 +1,5 @@
7+package repository
8+
9+import "errors"
10+
11+var ErrRecordNotFound = errors.New("record not found")
12diff --git a/pkg/database/sql/user.go b/pkg/database/sql/user.go
13index 6b1cf0fd16436f1cbce0e4c0c94d23d372ec31b9..2ec86229435c1508c25f5b67f98178a6cfd67647 100644
14--- a/pkg/database/sql/user.go
15+++ b/pkg/database/sql/user.go
16@@ -2,6 +2,7 @@ package sql
17
18 import (
19 "context"
20+ "errors"
21
22 "golang.org/x/crypto/bcrypt"
23 "gorm.io/gorm"
24@@ -82,7 +83,7 @@ WithContext(ctx).
25 Find(&users)
26
27 if result.Error != nil {
28- return nil, result.Error
29+ return nil, wrapError(result.Error)
30 }
31
32 return users.ToModel(), nil
33@@ -95,7 +96,7 @@ WithContext(ctx).
34 First(user)
35
36 if result.Error != nil {
37- return nil, result.Error
38+ return nil, wrapError(result.Error)
39 }
40
41 return user, nil
42@@ -113,7 +114,7 @@ Where("username = ?", username).
43 First(&userID)
44
45 if result.Error != nil {
46- return 0, result.Error
47+ return 0, wrapError(result.Error)
48 }
49
50 return userID.ID, nil
51@@ -131,7 +132,7 @@ Where("id = ?", id).
52 First(&userPassword)
53
54 if result.Error != nil {
55- return nil, result.Error
56+ return nil, wrapError(result.Error)
57 }
58
59 return userPassword.Password, nil
60@@ -150,7 +151,7 @@ result := self.db.
61 WithContext(ctx).
62 Create(user)
63 if result.Error != nil {
64- return 0, result.Error
65+ return 0, wrapError(result.Error)
66 }
67
68 return user.Model.ID, nil
69@@ -172,7 +173,7 @@ WithContext(ctx).
70 Omit("password").
71 Updates(user)
72 if result.Error != nil {
73- return result.Error
74+ return wrapError(result.Error)
75 }
76
77 return nil
78@@ -189,7 +190,7 @@ result := self.db.
79 WithContext(ctx).
80 Delete(user)
81 if result.Error != nil {
82- return result.Error
83+ return wrapError(result.Error)
84 }
85 return nil
86 }
87@@ -203,7 +204,7 @@ Select("count(id) > 0").
88 Find(&exists)
89
90 if result.Error != nil {
91- return false, result.Error
92+ return false, wrapError(result.Error)
93 }
94
95 return exists, nil
96@@ -220,7 +221,7 @@ Where("id = ?", id).
97 First(&userPath)
98
99 if result.Error != nil {
100- return "", result.Error
101+ return "", wrapError(result.Error)
102 }
103
104 return userPath, nil
105@@ -233,5 +234,12 @@ Model(&User{}).
106 Where("id = ?", id).
107 Update("password", password)
108
109- return result.Error
110+ return wrapError(result.Error)
111+}
112+
113+func wrapError(err error) error {
114+ if errors.Is(err, gorm.ErrRecordNotFound) {
115+ return repository.ErrRecordNotFound
116+ }
117+ return err
118 }
119diff --git a/pkg/service/auth.go b/pkg/service/auth.go
120index 30e574a9690f2c33f6a89f08fa089f8776020102..2fc06e383f8f34d2e1ac843d1d48fb1930c6f80a 100644
121--- a/pkg/service/auth.go
122+++ b/pkg/service/auth.go
123@@ -21,6 +21,8 @@ userRepository repository.UserRepository
124 key []byte
125 }
126
127+var InvalidLogin = errors.New("Invalid login")
128+
129 func NewAuthController(
130 authRepository repository.AuthRepository,
131 userRepository repository.UserRepository,
132@@ -35,17 +37,21 @@ }
133
134 func (c *AuthController) Login(ctx context.Context, username, password []byte) ([]byte, error) {
135 id, err := c.authRepository.GetIDByUsername(ctx, string(username))
136- if err != nil {
137+ if errors.Is(err, repository.ErrRecordNotFound) {
138+ return nil, InvalidLogin
139+ } else if err != nil {
140 return nil, err
141 }
142
143 hashedPassword, err := c.authRepository.GetPassword(ctx, id)
144- if err != nil {
145+ if errors.Is(err, repository.ErrRecordNotFound) {
146+ return nil, InvalidLogin
147+ } else if err != nil {
148 return nil, err
149 }
150
151 if err := bcrypt.CompareHashAndPassword(hashedPassword, password); err != nil {
152- return nil, err
153+ return nil, InvalidLogin
154 }
155
156 token := &Token{
157diff --git a/pkg/view/auth.go b/pkg/view/auth.go
158index 1b87235ed7a45f011cc695a650c793d3d493bc91..8d870352e8b7220c8df36582adaeeab7beb054b1 100644
159--- a/pkg/view/auth.go
160+++ b/pkg/view/auth.go
161@@ -2,6 +2,7 @@ package view
162
163 import (
164 "encoding/base64"
165+ "errors"
166 "net/http"
167
168 "git.sr.ht/~gabrielgio/img/pkg/ext"
169@@ -45,6 +46,15 @@ password = []byte(r.FormValue("password"))
170 )
171
172 auth, err := v.userController.Login(r.Context(), username, password)
173+
174+ if errors.Is(err, service.InvalidLogin) {
175+ templates.WritePageTemplate(w, &templates.LoginPage{
176+ Username: r.FormValue("username"),
177+ Err: err.Error(),
178+ })
179+ return nil
180+ }
181+
182 if err != nil {
183 return err
184 }
185diff --git a/scss/main.scss b/scss/main.scss
186index 887745238075345e54a2f47e69baa22d7e1beb31..532a38a801bf6cc40f6e57780c6fc35505883439 100644
187--- a/scss/main.scss
188+++ b/scss/main.scss
189@@ -1,13 +1,11 @@
190 $breakpoint: 360px;
191-
192+$radius: 0px;
193 $tablet: 480px;
194 $body-font-size: 1rem;
195-$radius-rounded: 0;
196
197 $navbar-breakpoint: $breakpoint;
198
199 $panel-item-border: 1px solid hsl(0, 0%, 93%);
200-$panel-radius: 0;
201 $panel-shadow: 0;
202
203 $card-shadow: 0;
204@@ -17,7 +15,6 @@
205 $table-cell-padding: 0.5em;
206 $table-cell-border-width: 0;
207
208-$tag-radius: 0;
209 $tag-delete-margin: 15px;
210
211 $title-weight: normal;
212diff --git a/templates/login.qtpl b/templates/login.qtpl
213index 56394d001c32c59db586e49112fb57fd71ea0463..c68fb5f8dd68488cbf0e1e5b2bc150c5fcfe806a 100644
214--- a/templates/login.qtpl
215+++ b/templates/login.qtpl
216@@ -1,5 +1,8 @@
217 {% code
218-type LoginPage struct {}
219+type LoginPage struct {
220+ Username string
221+ Err string
222+ }
223 %}
224
225 {% func (p *LoginPage) Title() %}Login{% endfunc %}
226@@ -9,7 +12,7 @@ <form action="/login" method="post">
227 <div class="field">
228 <label class="label">Username</label>
229 <div class="control">
230- <input class="input" name="username" type="text">
231+ <input class="input" name="username" value="{%s p.Username %}" type="text">
232 </div>
233 </div>
234 <div class="field">
235@@ -18,9 +21,18 @@ <div class="control">
236 <input class="input" name="password" type="password">
237 </div>
238 </div>
239+ <div class="field is-grouped is-grouped-right">
240+ <input class="button" value="login" type="submit">
241+ </div>
242+ {% if p.Err != "" %}
243 <div class="field">
244- <input class="button is-pulled-right" value="login" type="submit">
245+ <article class="message is-danger">
246+ <div class="message-body">
247+ {%s p.Err %}
248+ </div>
249+ </article>
250 </div>
251+ {% endif %}
252 </form>
253 {% endfunc %}
254
255diff --git a/templates/register.qtpl b/templates/register.qtpl
256index 115edfeab3e33fe1f11b2dbf6afc9f3587e40a88..4d3c545dd718c85dc5e8395cdeb5e2694370745a 100644
257--- a/templates/register.qtpl
258+++ b/templates/register.qtpl
259@@ -25,8 +25,8 @@ <div class="control">
260 <input class="input" name="path" type="text">
261 </div>
262 </div>
263- <div class="field">
264- <input class="button is-pulled-right" value="Save" type="submit">
265+ <div class="field is-grouped is-grouped-right">
266+ <input class="button" value="Save" type="submit">
267 </div>
268 </form>
269 {% endfunc %}