user.go 15.4 KB
Newer Older
1 2 3 4 5
package handlers

import (
	"context"
	"net/http"
Lee Brown's avatar
Lee Brown committed
6
	"strconv"
7
	"strings"
David Fleming's avatar
David Fleming committed
8
	"time"
9

10 11
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
12 13
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
	"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
14
	"geeks-accelerator/oss/saas-starter-kit/internal/user"
15
	"geeks-accelerator/oss/saas-starter-kit/internal/user_auth"
Lee Brown's avatar
Lee Brown committed
16
	"github.com/jmoiron/sqlx"
17
	"github.com/pkg/errors"
18
	"gopkg.in/go-playground/validator.v9"
19 20
)

Lee Brown's avatar
Lee Brown committed
21 22 23
// sessionTtl defines the auth token expiration.
var sessionTtl = time.Hour * 24

24 25
// User represents the User API method handler set.
type User struct {
Lee Brown's avatar
Lee Brown committed
26
	MasterDB       *sqlx.DB
27
	TokenGenerator user_auth.TokenGenerator
28 29 30 31

	// ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE.
}

32
// Find godoc
33
// TODO: Need to implement unittests on users/find endpoint. There are none.
34 35 36 37 38 39 40 41 42 43
// @Summary List users
// @Description Find returns the existing users in the system.
// @Tags user
// @Accept  json
// @Produce  json
// @Security OAuth2Password
// @Param where				query string 	false	"Filter string, example: name = 'Company Name' and email = '[email protected]'"
// @Param order				query string   	false 	"Order columns separated by comma, example: created_at desc"
// @Param limit				query integer  	false 	"Limit, example: 10"
// @Param offset			query integer  	false 	"Offset, example: 20"
44
// @Param include-archived query boolean 	false 	"Included Archived, example: false"
45 46 47 48
// @Success 200 {array} user.UserResponse
// @Failure 400 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
// @Router /users [get]
Lee Brown's avatar
Lee Brown committed
49
func (u *User) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
David Fleming's avatar
David Fleming committed
50 51 52 53 54 55
	claims, ok := ctx.Value(auth.Key).(auth.Claims)
	if !ok {
		return errors.New("claims missing from context")
	}

	var req user.UserFindRequest
56 57 58 59 60

	// Handle where query value if set.
	if v := r.URL.Query().Get("where"); v != "" {
		where, args, err := web.ExtractWhereArgs(v)
		if err != nil {
Lee Brown's avatar
Lee Brown committed
61
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
62
		}
Lee Brown's avatar
Lee Brown committed
63
		req.Where = where
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
		req.Args = args
	}

	// Handle order query value if set.
	if v := r.URL.Query().Get("order"); v != "" {
		for _, o := range strings.Split(v, ",") {
			o = strings.TrimSpace(o)
			if o != "" {
				req.Order = append(req.Order, o)
			}
		}
	}

	// Handle limit query value if set.
	if v := r.URL.Query().Get("limit"); v != "" {
		l, err := strconv.Atoi(v)
		if err != nil {
			err = errors.WithMessagef(err, "unable to parse %s as int for limit param", v)
Lee Brown's avatar
Lee Brown committed
82
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
83 84 85 86 87 88 89 90 91 92
		}
		ul := uint(l)
		req.Limit = &ul
	}

	// Handle offset query value if set.
	if v := r.URL.Query().Get("offset"); v != "" {
		l, err := strconv.Atoi(v)
		if err != nil {
			err = errors.WithMessagef(err, "unable to parse %s as int for offset param", v)
Lee Brown's avatar
Lee Brown committed
93
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
94 95 96 97 98
		}
		ul := uint(l)
		req.Limit = &ul
	}

99 100
	// Handle include-archived query value if set.
	if v := r.URL.Query().Get("include-archived"); v != "" {
101 102
		b, err := strconv.ParseBool(v)
		if err != nil {
103
			err = errors.WithMessagef(err, "unable to parse %s as boolean for include-archived param", v)
Lee Brown's avatar
Lee Brown committed
104
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
105
		}
106
		req.IncludeArchived = b
107 108
	}

Lee Brown's avatar
Lee Brown committed
109 110
	//if err := web.Decode(r, &req); err != nil {
	//	if _, ok := errors.Cause(err).(*web.Error); !ok {
Lee Brown's avatar
Lee Brown committed
111
	//		err = weberror.NewError(ctx, err, http.StatusBadRequest)
Lee Brown's avatar
Lee Brown committed
112 113 114
	//	}
	//	return  web.RespondJsonError(ctx, w, err)
	//}
David Fleming's avatar
David Fleming committed
115

Lee Brown's avatar
Lee Brown committed
116
	res, err := user.Find(ctx, claims, u.MasterDB, req)
117 118 119 120
	if err != nil {
		return err
	}

121 122 123 124 125 126
	var resp []*user.UserResponse
	for _, m := range res {
		resp = append(resp, m.Response(ctx))
	}

	return web.RespondJson(ctx, w, resp, http.StatusOK)
127 128
}

129
// Read godoc
130 131
// @Summary Get user by ID
// @Description Read returns the specified user from the system.
132 133 134
// @Tags user
// @Accept  json
// @Produce  json
135 136
// @Security OAuth2Password
// @Param id path string true "User ID"
137 138 139 140
// @Success 200 {object} user.UserResponse
// @Failure 400 {object} web.ErrorResponse
// @Failure 404 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
141
// @Router /users/{id} [get]
Lee Brown's avatar
Lee Brown committed
142
func (u *User) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
143 144 145 146 147
	claims, ok := ctx.Value(auth.Key).(auth.Claims)
	if !ok {
		return errors.New("claims missing from context")
	}

148
	// Handle include-archived query value if set.
Lee Brown's avatar
Lee Brown committed
149
	var includeArchived bool
150
	if v := r.URL.Query().Get("include-archived"); v != "" {
Lee Brown's avatar
Lee Brown committed
151
		b, err := strconv.ParseBool(v)
Lee Brown's avatar
Lee Brown committed
152
		if err != nil {
153
			err = errors.WithMessagef(err, "unable to parse %s as boolean for include-archived param", v)
Lee Brown's avatar
Lee Brown committed
154
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
Lee Brown's avatar
Lee Brown committed
155
		}
Lee Brown's avatar
Lee Brown committed
156
		includeArchived = b
Lee Brown's avatar
Lee Brown committed
157 158
	}

159 160 161 162
	res, err := user.Read(ctx, claims, u.MasterDB, user.UserReadRequest{
		ID:              params["id"],
		IncludeArchived: includeArchived,
	})
163
	if err != nil {
Lee Brown's avatar
Lee Brown committed
164 165
		cause := errors.Cause(err)
		switch cause {
166
		case user.ErrNotFound:
Lee Brown's avatar
Lee Brown committed
167
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusNotFound))
168
		default:
Lee Brown's avatar
Lee Brown committed
169
			return errors.Wrapf(err, "ID: %s", params["id"])
170 171 172
		}
	}

173
	return web.RespondJson(ctx, w, res.Response(ctx), http.StatusOK)
174 175
}

176 177 178 179 180 181 182 183
// Create godoc
// @Summary Create new user.
// @Description Create inserts a new user into the system.
// @Tags user
// @Accept  json
// @Produce  json
// @Security OAuth2Password
// @Param data body user.UserCreateRequest true "User details"
Lee Brown's avatar
Lee Brown committed
184
// @Success 201 {object} user.UserResponse
185 186 187 188
// @Failure 400 {object} web.ErrorResponse
// @Failure 403 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
// @Router /users [post]
189
func (u *User) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
Lee Brown's avatar
Lee Brown committed
190 191 192
	v, err := webcontext.ContextValues(ctx)
	if err != nil {
		return err
Lee Brown's avatar
Lee Brown committed
193 194
	}

Lee Brown's avatar
Lee Brown committed
195 196 197
	claims, err := auth.ClaimsFromContext(ctx)
	if err != nil {
		return err
David Fleming's avatar
David Fleming committed
198 199
	}

Lee Brown's avatar
Lee Brown committed
200
	var req user.UserCreateRequest
Lee Brown's avatar
Lee Brown committed
201 202 203
	if err := web.Decode(ctx, r, &req); err != nil {
		if _, ok := errors.Cause(err).(*weberror.Error); !ok {
			err = weberror.NewError(ctx, err, http.StatusBadRequest)
204
		}
205
		return web.RespondJsonError(ctx, w, err)
Lee Brown's avatar
Lee Brown committed
206 207 208 209
	}

	res, err := user.Create(ctx, claims, u.MasterDB, req, v.Now)
	if err != nil {
Lee Brown's avatar
Lee Brown committed
210 211
		cause := errors.Cause(err)
		switch cause {
Lee Brown's avatar
Lee Brown committed
212
		case user.ErrForbidden:
Lee Brown's avatar
Lee Brown committed
213
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden))
Lee Brown's avatar
Lee Brown committed
214
		default:
Lee Brown's avatar
Lee Brown committed
215
			_, ok := cause.(validator.ValidationErrors)
216
			if ok {
Lee Brown's avatar
Lee Brown committed
217
				return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
218 219
			}

Lee Brown's avatar
Lee Brown committed
220 221 222 223
			return errors.Wrapf(err, "User: %+v", &req)
		}
	}

224
	return web.RespondJson(ctx, w, res.Response(ctx), http.StatusCreated)
Lee Brown's avatar
Lee Brown committed
225 226
}

227 228 229 230 231 232 233 234
// Read godoc
// @Summary Update user by ID
// @Description Update updates the specified user in the system.
// @Tags user
// @Accept  json
// @Produce  json
// @Security OAuth2Password
// @Param data body user.UserUpdateRequest true "Update fields"
Lee Brown's avatar
Lee Brown committed
235
// @Success 204
236 237 238 239
// @Failure 400 {object} web.ErrorResponse
// @Failure 403 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
// @Router /users [patch]
Lee Brown's avatar
Lee Brown committed
240
func (u *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
Lee Brown's avatar
Lee Brown committed
241 242 243
	v, err := webcontext.ContextValues(ctx)
	if err != nil {
		return err
244 245
	}

Lee Brown's avatar
Lee Brown committed
246 247 248
	claims, err := auth.ClaimsFromContext(ctx)
	if err != nil {
		return err
Lee Brown's avatar
Lee Brown committed
249 250 251
	}

	var req user.UserUpdateRequest
Lee Brown's avatar
Lee Brown committed
252 253 254
	if err := web.Decode(ctx, r, &req); err != nil {
		if _, ok := errors.Cause(err).(*weberror.Error); !ok {
			err = weberror.NewError(ctx, err, http.StatusBadRequest)
255
		}
256
		return web.RespondJsonError(ctx, w, err)
257 258
	}

Lee Brown's avatar
Lee Brown committed
259
	err = user.Update(ctx, claims, u.MasterDB, req, v.Now)
260
	if err != nil {
Lee Brown's avatar
Lee Brown committed
261 262
		cause := errors.Cause(err)
		switch cause {
Lee Brown's avatar
Lee Brown committed
263
		case user.ErrForbidden:
Lee Brown's avatar
Lee Brown committed
264
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden))
Lee Brown's avatar
Lee Brown committed
265
		default:
Lee Brown's avatar
Lee Brown committed
266
			_, ok := cause.(validator.ValidationErrors)
267
			if ok {
Lee Brown's avatar
Lee Brown committed
268
				return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
269 270
			}

Lee Brown's avatar
Lee Brown committed
271
			return errors.Wrapf(err, "Id: %s User: %+v", req.ID, &req)
Lee Brown's avatar
Lee Brown committed
272
		}
273 274
	}

Lee Brown's avatar
Lee Brown committed
275
	return web.RespondJson(ctx, w, nil, http.StatusNoContent)
276 277
}

278 279 280 281 282 283 284 285
// Read godoc
// @Summary Update user password by ID
// @Description Update updates the password for a specified user in the system.
// @Tags user
// @Accept  json
// @Produce  json
// @Security OAuth2Password
// @Param data body user.UserUpdatePasswordRequest true "Update fields"
Lee Brown's avatar
Lee Brown committed
286
// @Success 204
287 288 289 290
// @Failure 400 {object} web.ErrorResponse
// @Failure 403 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
// @Router /users/password [patch]
Lee Brown's avatar
Lee Brown committed
291
func (u *User) UpdatePassword(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
Lee Brown's avatar
Lee Brown committed
292 293 294
	v, err := webcontext.ContextValues(ctx)
	if err != nil {
		return err
Lee Brown's avatar
Lee Brown committed
295 296
	}

Lee Brown's avatar
Lee Brown committed
297 298 299
	claims, err := auth.ClaimsFromContext(ctx)
	if err != nil {
		return err
David Fleming's avatar
David Fleming committed
300 301
	}

Lee Brown's avatar
Lee Brown committed
302
	var req user.UserUpdatePasswordRequest
Lee Brown's avatar
Lee Brown committed
303 304 305
	if err := web.Decode(ctx, r, &req); err != nil {
		if _, ok := errors.Cause(err).(*weberror.Error); !ok {
			err = weberror.NewError(ctx, err, http.StatusBadRequest)
306
		}
307
		return web.RespondJsonError(ctx, w, err)
Lee Brown's avatar
Lee Brown committed
308
	}
David Fleming's avatar
David Fleming committed
309

Lee Brown's avatar
Lee Brown committed
310
	err = user.UpdatePassword(ctx, claims, u.MasterDB, req, v.Now)
Lee Brown's avatar
Lee Brown committed
311
	if err != nil {
Lee Brown's avatar
Lee Brown committed
312 313
		cause := errors.Cause(err)
		switch cause {
Lee Brown's avatar
Lee Brown committed
314
		case user.ErrNotFound:
Lee Brown's avatar
Lee Brown committed
315
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusNotFound))
Lee Brown's avatar
Lee Brown committed
316
		case user.ErrForbidden:
Lee Brown's avatar
Lee Brown committed
317
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden))
Lee Brown's avatar
Lee Brown committed
318
		default:
Lee Brown's avatar
Lee Brown committed
319
			_, ok := cause.(validator.ValidationErrors)
320
			if ok {
Lee Brown's avatar
Lee Brown committed
321
				return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
322 323 324
			}

			return errors.Wrapf(err, "Id: %s  User: %+v", req.ID, &req)
Lee Brown's avatar
Lee Brown committed
325 326 327 328 329 330
		}
	}

	return web.RespondJson(ctx, w, nil, http.StatusNoContent)
}

331 332 333 334 335 336 337 338
// Read godoc
// @Summary Archive user by ID
// @Description Archive soft-deletes the specified user from the system.
// @Tags user
// @Accept  json
// @Produce  json
// @Security OAuth2Password
// @Param data body user.UserArchiveRequest true "Update fields"
Lee Brown's avatar
Lee Brown committed
339
// @Success 204
340 341 342 343
// @Failure 400 {object} web.ErrorResponse
// @Failure 403 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
// @Router /users/archive [patch]
Lee Brown's avatar
Lee Brown committed
344
func (u *User) Archive(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
Lee Brown's avatar
Lee Brown committed
345 346 347
	v, err := webcontext.ContextValues(ctx)
	if err != nil {
		return err
348 349
	}

Lee Brown's avatar
Lee Brown committed
350 351 352
	claims, err := auth.ClaimsFromContext(ctx)
	if err != nil {
		return err
353 354
	}

355
	var req user.UserArchiveRequest
Lee Brown's avatar
Lee Brown committed
356 357 358
	if err := web.Decode(ctx, r, &req); err != nil {
		if _, ok := errors.Cause(err).(*weberror.Error); !ok {
			err = weberror.NewError(ctx, err, http.StatusBadRequest)
359
		}
360
		return web.RespondJsonError(ctx, w, err)
361 362
	}

Lee Brown's avatar
Lee Brown committed
363
	err = user.Archive(ctx, claims, u.MasterDB, req, v.Now)
364
	if err != nil {
Lee Brown's avatar
Lee Brown committed
365 366
		cause := errors.Cause(err)
		switch cause {
367
		case user.ErrForbidden:
Lee Brown's avatar
Lee Brown committed
368
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden))
369
		default:
Lee Brown's avatar
Lee Brown committed
370
			_, ok := cause.(validator.ValidationErrors)
371
			if ok {
Lee Brown's avatar
Lee Brown committed
372
				return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
373 374 375
			}

			return errors.Wrapf(err, "Id: %s", req.ID)
376 377 378
		}
	}

Lee Brown's avatar
Lee Brown committed
379
	return web.RespondJson(ctx, w, nil, http.StatusNoContent)
380 381
}

382 383 384 385 386 387 388 389
// Delete godoc
// @Summary Delete user by ID
// @Description Delete removes the specified user from the system.
// @Tags user
// @Accept  json
// @Produce  json
// @Security OAuth2Password
// @Param id path string true "User ID"
Lee Brown's avatar
Lee Brown committed
390
// @Success 204
391 392 393 394
// @Failure 400 {object} web.ErrorResponse
// @Failure 403 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
// @Router /users/{id} [delete]
395
func (u *User) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
Lee Brown's avatar
Lee Brown committed
396 397 398
	claims, err := auth.ClaimsFromContext(ctx)
	if err != nil {
		return err
David Fleming's avatar
David Fleming committed
399 400
	}

401 402
	err = user.Delete(ctx, claims, u.MasterDB,
		user.UserDeleteRequest{ID: params["id"]})
403
	if err != nil {
Lee Brown's avatar
Lee Brown committed
404 405
		cause := errors.Cause(err)
		switch cause {
406
		case user.ErrForbidden:
Lee Brown's avatar
Lee Brown committed
407
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden))
408
		default:
409 410
			_, ok := cause.(validator.ValidationErrors)
			if ok {
Lee Brown's avatar
Lee Brown committed
411
				return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
412 413 414
			}

			return errors.Wrapf(err, "Id: %s", params["id"])
415 416 417
		}
	}

Lee Brown's avatar
Lee Brown committed
418
	return web.RespondJson(ctx, w, nil, http.StatusNoContent)
419 420
}

421 422 423 424 425 426 427 428
// SwitchAccount godoc
// @Summary Switch account.
// @Description SwitchAccount updates the auth claims to a new account.
// @Tags user
// @Accept  json
// @Produce  json
// @Security OAuth2Password
// @Param account_id path int true "Account ID"
429
// @Success 200
430
// @Failure 400 {object} web.ErrorResponse
431
// @Failure 401 {object} web.ErrorResponse
432 433
// @Failure 500 {object} web.ErrorResponse
// @Router /users/switch-account/{account_id} [patch]
Lee Brown's avatar
Lee Brown committed
434
func (u *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
Lee Brown's avatar
Lee Brown committed
435 436 437
	v, err := webcontext.ContextValues(ctx)
	if err != nil {
		return err
Lee Brown's avatar
Lee Brown committed
438 439
	}

Lee Brown's avatar
Lee Brown committed
440 441 442
	claims, err := auth.ClaimsFromContext(ctx)
	if err != nil {
		return err
Lee Brown's avatar
Lee Brown committed
443 444
	}

Lee Brown's avatar
Lee Brown committed
445 446 447
	tkn, err := user_auth.SwitchAccount(ctx, u.MasterDB, u.TokenGenerator, claims, user_auth.SwitchAccountRequest{
		AccountID: params["account_id"],
	}, sessionTtl, v.Now)
Lee Brown's avatar
Lee Brown committed
448
	if err != nil {
Lee Brown's avatar
Lee Brown committed
449 450
		cause := errors.Cause(err)
		switch cause {
451
		case user_auth.ErrAuthenticationFailure:
Lee Brown's avatar
Lee Brown committed
452
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusUnauthorized))
Lee Brown's avatar
Lee Brown committed
453
		default:
Lee Brown's avatar
Lee Brown committed
454
			_, ok := cause.(validator.ValidationErrors)
455
			if ok {
Lee Brown's avatar
Lee Brown committed
456
				return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
457 458
			}

Lee Brown's avatar
Lee Brown committed
459 460 461 462
			return errors.Wrap(err, "switch account")
		}
	}

463
	return web.RespondJson(ctx, w, tkn, http.StatusOK)
Lee Brown's avatar
Lee Brown committed
464 465
}

466 467 468 469 470 471 472
// Token godoc
// @Summary Token handles a request to authenticate a user.
// @Description Token generates an oauth2 accessToken using Basic Auth with a user's email and password.
// @Tags user
// @Accept  json
// @Produce  json
// @Security BasicAuth
473
// @Param scope query string false "Scope" Enums(user, admin)
474 475 476 477
// @Success 200
// @Failure 400 {object} web.ErrorResponse
// @Failure 401 {object} web.ErrorResponse
// @Failure 500 {object} web.ErrorResponse
478
// @Router /oauth/token [post]
479
func (u *User) Token(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
Lee Brown's avatar
Lee Brown committed
480 481 482
	v, err := webcontext.ContextValues(ctx)
	if err != nil {
		return err
483 484 485 486 487
	}

	email, pass, ok := r.BasicAuth()
	if !ok {
		err := errors.New("must provide email and password in Basic auth")
Lee Brown's avatar
Lee Brown committed
488
		return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusUnauthorized))
489 490
	}

Lee Brown's avatar
Lee Brown committed
491 492
	accountID := r.URL.Query().Get("account_id")

493 494 495
	// Optional to include scope.
	scope := r.URL.Query().Get("scope")

Lee Brown's avatar
Lee Brown committed
496 497 498 499 500
	tkn, err := user_auth.Authenticate(ctx, u.MasterDB, u.TokenGenerator, user_auth.AuthenticateRequest{
		Email:     email,
		Password:  pass,
		AccountID: accountID,
	}, sessionTtl, v.Now, scope)
501
	if err != nil {
Lee Brown's avatar
Lee Brown committed
502 503
		cause := errors.Cause(err)
		switch cause {
504
		case user_auth.ErrAuthenticationFailure:
Lee Brown's avatar
Lee Brown committed
505
			return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusUnauthorized))
506
		default:
507 508
			_, ok := cause.(validator.ValidationErrors)
			if ok {
Lee Brown's avatar
Lee Brown committed
509
				return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest))
510 511
			}

512 513 514 515
			return errors.Wrap(err, "authenticating")
		}
	}

Lee Brown's avatar
Lee Brown committed
516
	return web.RespondJson(ctx, w, tkn, http.StatusOK)
517
}