1
1
package coderd
2
2
3
3
import (
4
+ "context"
5
+ "database/sql"
6
+ "errors"
7
+ "fmt"
4
8
"net/http"
5
9
"time"
6
10
7
11
"github.com/go-chi/render"
12
+ "github.com/google/uuid"
8
13
14
+ "github.com/coder/coder/coderd/userpassword"
9
15
"github.com/coder/coder/database"
16
+ "github.com/coder/coder/httpapi"
10
17
"github.com/coder/coder/httpmw"
11
18
)
12
19
13
20
type User struct {
14
- ID string `json:"id"`
15
- Email string `json:"email"`
16
- CreatedAt time.Time `json:"created_at"`
17
- Username string `json:"username"`
21
+ ID string `json:"id" validate:"required"`
22
+ Email string `json:"email" validate:"required"`
23
+ CreatedAt time.Time `json:"created_at" validate:"required"`
24
+ Username string `json:"username" validate:"required"`
25
+ }
26
+
27
+ type CreateUserRequest struct {
28
+ Email string `json:"email" validate:"required,email"`
29
+ Username string `json:"username" validate:"required,username"`
30
+ Password string `json:"password" validate:"required"`
31
+ }
32
+
33
+ type LoginWithPasswordRequest struct {
34
+ Email string `json:"email" validate:"required,email"`
35
+ Password string `json:"password" validate:"required"`
36
+ }
37
+
38
+ type LoginWithPasswordResponse struct {
39
+ SessionToken string `json:"session_token" validate:"required"`
18
40
}
19
41
20
42
type users struct {
21
43
Database database.Store
22
44
}
23
45
46
+ // Creates the initial user for a Coder deployment.
47
+ func (users * users ) createInitialUser (rw http.ResponseWriter , r * http.Request ) {
48
+ var createUser CreateUserRequest
49
+ if ! httpapi .Read (rw , r , & createUser ) {
50
+ return
51
+ }
52
+ userCount , err := users .Database .GetUserCount (r .Context ())
53
+ if err != nil {
54
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
55
+ Message : fmt .Sprintf ("get user count: %s" , err .Error ()),
56
+ })
57
+ return
58
+ }
59
+ if userCount != 0 {
60
+ httpapi .Write (rw , http .StatusForbidden , httpapi.Response {
61
+ Message : "the initial user has already been created" ,
62
+ })
63
+ return
64
+ }
65
+ user , err := users .Database .GetUserByEmailOrUsername (r .Context (), database.GetUserByEmailOrUsernameParams {
66
+ Email : createUser .Email ,
67
+ Username : createUser .Username ,
68
+ })
69
+ if errors .Is (err , sql .ErrNoRows ) {
70
+ err = nil
71
+ }
72
+ if err != nil {
73
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
74
+ Message : fmt .Sprintf ("get user: %s" , err .Error ()),
75
+ })
76
+ return
77
+ }
78
+ hashedPassword , err := userpassword .Hash (createUser .Password )
79
+ if err != nil {
80
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
81
+ Message : fmt .Sprintf ("hash password: %s" , err .Error ()),
82
+ })
83
+ return
84
+ }
85
+
86
+ user , err = users .Database .InsertUser (context .Background (), database.InsertUserParams {
87
+ ID : uuid .NewString (),
88
+ Email : createUser .Email ,
89
+ HashedPassword : []byte (hashedPassword ),
90
+ Username : createUser .Username ,
91
+ LoginType : database .LoginTypeBuiltIn ,
92
+ CreatedAt : database .Now (),
93
+ UpdatedAt : database .Now (),
94
+ })
95
+ if err != nil {
96
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
97
+ Message : fmt .Sprintf ("create user: %s" , err .Error ()),
98
+ })
99
+ return
100
+ }
101
+ render .Status (r , http .StatusCreated )
102
+ render .JSON (rw , r , user )
103
+ }
104
+
105
+ // Returns the currently authenticated user.
24
106
func (users * users ) getAuthenticatedUser (rw http.ResponseWriter , r * http.Request ) {
25
107
user := httpmw .User (r )
26
108
@@ -31,3 +113,61 @@ func (users *users) getAuthenticatedUser(rw http.ResponseWriter, r *http.Request
31
113
Username : user .Username ,
32
114
})
33
115
}
116
+
117
+ func (users * users ) loginWithPassword (rw http.ResponseWriter , r * http.Request ) {
118
+ var loginWithPassword LoginWithPasswordRequest
119
+ if ! httpapi .Read (rw , r , loginWithPassword ) {
120
+ return
121
+ }
122
+ user , err := users .Database .GetUserByEmailOrUsername (r .Context (), database.GetUserByEmailOrUsernameParams {
123
+ Email : loginWithPassword .Email ,
124
+ })
125
+ if errors .Is (err , sql .ErrNoRows ) {
126
+ httpapi .Write (rw , http .StatusUnauthorized , httpapi.Response {
127
+ Message : "invalid email or password" ,
128
+ })
129
+ return
130
+ }
131
+ if err != nil {
132
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
133
+ Message : fmt .Sprintf ("get user: %s" , err .Error ()),
134
+ })
135
+ return
136
+ }
137
+ equal , err := userpassword .Compare (string (user .HashedPassword ), loginWithPassword .Password )
138
+ if err != nil {
139
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
140
+ Message : fmt .Sprintf ("compare: %s" , err .Error ()),
141
+ })
142
+ }
143
+ if ! equal {
144
+ // This message is the same as above to remove ease in detecting whether
145
+ // users are registered or not. Attackers still could with a timing attack.
146
+ httpapi .Write (rw , http .StatusUnauthorized , httpapi.Response {
147
+ Message : "invalid email or password" ,
148
+ })
149
+ return
150
+ }
151
+
152
+ // key, secret := (&database.APIKey{
153
+ // UserID: actor.ID,
154
+ // ExpiresAt: expiresAt,
155
+ // LoginType: actor.LoginType,
156
+ // OIDCAccessToken: oidcCfg.AccessToken,
157
+ // OIDCRefreshToken: oidcCfg.RefreshToken,
158
+ // OIDCIDToken: oidcCfg.IDToken,
159
+ // // OIDCExpiry indicates when we need to fetch a new OIDC token.
160
+ // OIDCExpiry: oidcCfg.Expiry,
161
+ // DevurlToken: devurlToken,
162
+ // }).Fill()
163
+
164
+ users .Database .InsertAPIKey (r .Context (), database.InsertAPIKeyParams {
165
+ ID : uuid .NewString (),
166
+ UserID : user .ID ,
167
+ ExpiresAt : database .Now ().Add (24 * time .Hour ),
168
+ CreatedAt : database .Now (),
169
+ UpdatedAt : database .Now (),
170
+ HashedSecret : []byte ("" ),
171
+ LoginType : database .LoginTypeBuiltIn ,
172
+ })
173
+ }
0 commit comments