1package web
2
3import (
4 "errors"
5 "log/slog"
6 "net/http"
7
8 "tug/internal/transport/web/text"
9)
10
11const GoogleLoginPath = "/login/google"
12
13func GoogleEntry() LoginEntry {
14 return LoginEntry{Label: text.LoginGoogle, Action: GoogleLoginPath}
15}
16
17var (
18 ErrLoginUnlinked = errors.New("web: no account is linked to this identity")
19 ErrLoginRefused = errors.New("web: this identity cannot sign in")
20 ErrLoginCancelled = errors.New("web: the sign-in was cancelled at the provider")
21 ErrLoginUnreachable = errors.New("web: the provider could not be reached or did not verify")
22 ErrStepExpired = errors.New("web: the round trip expired or was already used")
23 ErrFormExpired = errors.New("web: the round trip came from another page")
24)
25
26func (k *Kit) FinishLogin(w http.ResponseWriter, r *http.Request, state, target string, err error, entries ...LoginEntry) {
27 if verr := validateEntries(entries); verr != nil {
28 slog.Error("login outcome refused", "err", verr)
29 k.renderError(w, r, http.StatusInternalServerError, text.ErrorInternalTitle, text.ErrorInternal)
30 return
31 }
32 state = echoed(state, echoStateBytes)
33 l := &login{kit: k, entries: entries}
34 form := func(status int, message text.Key) {
35 l.render(w, r, status, loginData{State: state, Message: message}, "")
36 }
37 switch {
38 case err == nil:
39 k.RotateCSRF(w)
40 k.Continue(w, r, target)
41 case errors.Is(err, ErrStepExpired):
42 slog.Info("external sign-in round trip refused", "err", err)
43 k.renderError(w, r, http.StatusBadRequest, text.ErrorBadRequestTitle, text.ContinuationInvalid)
44 case errors.Is(err, ErrFormExpired):
45 slog.Warn("external sign-in round trip refused", "err", err)
46 k.renderError(w, r, http.StatusForbidden, text.ErrorForbiddenTitle, text.ErrorFormExpired)
47 case errors.Is(err, ErrLoginExpired):
48 k.renderError(w, r, http.StatusBadRequest, text.ErrorBadRequestTitle, text.LoginExpired)
49 case errors.Is(err, ErrLoginElsewhere):
50 k.renderError(w, r, http.StatusBadRequest, text.ErrorBadRequestTitle, text.LoginElsewhere)
51 case errors.Is(err, ErrLoginUnlinked):
52 k.RenderLoginRejected(w, r, state, entries...)
53 case errors.Is(err, ErrLoginBusy):
54 form(http.StatusServiceUnavailable, text.LoginBusy)
55 case errors.Is(err, ErrLoginStale):
56 form(http.StatusOK, text.LoginStale)
57 case errors.Is(err, ErrLoginRefused):
58 slog.Warn("external sign-in refused", "err", err)
59 form(http.StatusOK, text.LoginGoogleRefused)
60 case errors.Is(err, ErrLoginCancelled):
61 form(http.StatusOK, text.LoginGoogleCancelled)
62 case errors.Is(err, ErrLoginUnreachable):
63 slog.Error("external sign-in failed at the provider", "err", err)
64 form(http.StatusBadGateway, text.LoginGoogleUnavailable)
65 default:
66 slog.Error("external sign-in failed", "err", err)
67 k.renderError(w, r, http.StatusInternalServerError, text.ErrorInternalTitle, text.ErrorInternal)
68 }
69}