Raw
 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}