summaryrefslogtreecommitdiff
path: root/app/controllers/sessions/controller.go
blob: bf7d813a02cc4342deba8bed8dccf14ec3a08f9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package sessions

import (
	"net/http"

	"github.com/xlgmokha/x/pkg/cookie"
	"github.com/xlgmokha/x/pkg/env"
	"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/cfg"
	"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/middleware"
	"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/oidc"
	"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls"
	"gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web"
)

type Controller struct {
	svc *Service
}

func New(cfg *oidc.OpenID, http *http.Client) *Controller {
	return &Controller{
		svc: NewService(cfg, http),
	}
}

func (c *Controller) MountTo(mux *http.ServeMux) {
	mux.HandleFunc("GET /session/new", c.New)
	mux.HandleFunc("GET /session", c.Show)
	mux.HandleFunc("GET /session/callback", c.Create)
	mux.HandleFunc("POST /session/destroy", c.Destroy)
}

func (c *Controller) New(w http.ResponseWriter, r *http.Request) {
	if middleware.IsLoggedIn(r) {
		http.Redirect(w, r, "/dashboard", http.StatusFound)
		return
	}

	url, nonce := c.svc.GenerateRedirectURL(r)
	cookie.Write(w, web.NewCookie(cfg.CSRFCookie, nonce))
	c.redirectTo(w, r, url)
}

func (c *Controller) Show(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie(cfg.IDTokenCookie)
	if err != nil {
		pls.LogError(r.Context(), err)
		w.WriteHeader(http.StatusNotFound)
		return
	}

	token, err := c.svc.JWTBody(r.Context(), oidc.RawToken(cookie.Value))
	if err != nil {
		pls.LogError(r.Context(), err)
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write(token)
}

/*
This is the callback endpoint from the OIDC Provider:

It will exchange a grant for the following tokens:

* access_token
* id_token
* refresh_token

# These tokens are encoded as a Base64 JSON string and stored in a session cookie

For Example:

The following is an example of the base64 value stored in the session cookie:

```base64
eyJhY2Nlc3NfdG9rZW4iOiJkNzQ2ZTVmMGQ2NmYyNTgxM2ZjYzIyYzNjY2E0YmYxYjNmOTQwMjQ5NTkxYTg4YzZmMDBjMDQzZTMxYTVkZDRhIiwidG9rZW5fdHlwZSI6IkJlYXJlciIsInJlZnJlc2hfdG9rZW4iOiIyZGUxNTFhMGQ2ZGJhNzU5MDVmNGRmZTRlZThkNmQ5MGNjZmRkYzYxYjJkNGRlNzU2ODRiYzFjYmY5YjE0ODBlIiwiZXhwaXJ5IjoiMjAyNS0wNC0yNVQxMTozODozOS4zNDAwNDc4MjQtMDY6MDAiLCJpZF90b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0pyYVdRaU9pSjBaREJUYldSS1VUUnhVR2cxY1U1TGVrMHlOakJEV0hneVZXZ3RkMmhITFUxRWFtOVBTMWRtZERoRklpd2lZV3huSWpvaVVsTXlOVFlpZlEuZXlKcGMzTWlPaUpvZEhSd09pOHZaMlJyTG5SbGMzUTZNekF3TUNJc0luTjFZaUk2SWpFaUxDSmhkV1FpT2lKbE16RmxNV1JoTUdJNFpqWmlObVV6TldOaE56QmpOemt3WWpFell6QTBNRFpsTkRSaFkyRTJZakppWmpZM1pqVTFaR1UzTXpVMVlUazNPV0V5TWpSbUlpd2laWGh3SWpveE56UTFOVGsxTmpNNUxDSnBZWFFpT2pFM05EVTFPVFUxTVRrc0ltRjFkR2hmZEdsdFpTSTZNVGMwTlRVd016TXpPQ3dpYzNWaVgyeGxaMkZqZVNJNklqSTBOelJqWmpCaU1qSXhNVFk0T0dFMU56STVOMkZqWlRCbE1qWXdZVEUxT1RRME56VTBaREUyWWpGaVpEUXlZemxrTmpjM09XTTVNREF6TmpjNE1EY2lMQ0p1WVcxbElqb2lRV1J0YVc1cGMzUnlZWFJ2Y2lJc0ltNXBZMnR1WVcxbElqb2ljbTl2ZENJc0luQnlaV1psY25KbFpGOTFjMlZ5Ym1GdFpTSTZJbkp2YjNRaUxDSmxiV0ZwYkNJNkltRmtiV2x1UUdWNFlXMXdiR1V1WTI5dElpd2laVzFoYVd4ZmRtVnlhV1pwWldRaU9uUnlkV1VzSW5CeWIyWnBiR1VpT2lKb2RIUndPaTh2WjJSckxuUmxjM1E2TXpBd01DOXliMjkwSWl3aWNHbGpkSFZ5WlNJNkltaDBkSEJ6T2k4dmQzZDNMbWR5WVhaaGRHRnlMbU52YlM5aGRtRjBZWEl2TWpVNFpEaGtZemt4Tm1SaU9HTmxZVEpqWVdaaU5tTXpZMlF3WTJJd01qUTJaV1psTURZeE5ESXhaR0prT0RObFl6TmhNelV3TkRJNFkyRmlaR0UwWmo5elBUZ3dKbVE5YVdSbGJuUnBZMjl1SWl3aVozSnZkWEJ6WDJScGNtVmpkQ0k2V3lKbmFYUnNZV0l0YjNKbklpd2lkRzl2YkdKdmVDSXNJbTFoYzNOZmFXNXpaWEowWDJkeWIzVndYMTh3WHpFd01DSXNJbU4xYzNSdmJTMXliMnhsY3kxeWIyOTBMV2R5YjNWd0wyRmhJaXdpWTNWemRHOXRMWEp2YkdWekxYSnZiM1F0WjNKdmRYQXZZV0V2WVdGaElpd2laMjUxZDJkbGRDSXNJa052YlcxcGREUTFNU0lzSW1waGMyaHJaVzVoY3lJc0ltWnNhV2RvZEdweklpd2lkSGRwZEhSbGNpSXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5SXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5OXpaV04xY21sMGVTSXNJalF4TWpjd09DSXNJbWRwZEd4aFlpMWxlR0Z0Y0d4bGN5OWtaVzF2TFdkeWIzVndJaXdpWTNWemRHOXRMWEp2YkdWekxYSnZiM1F0WjNKdmRYQWlMQ0kwTXpRd05EUXRaM0p2ZFhBdE1TSXNJalF6TkRBME5DMW5jbTkxY0MweUlpd2laMmwwYkdGaUxXOXlaekVpTENKbmFYUnNZV0l0YjNKbkwzTmxZM1Z5WlNJc0ltZHBkR3hoWWkxdmNtY3ZjMlZqZFhKbEwyMWhibUZuWlhKeklpd2laMmwwYkdGaUxXOXlaeTl6WldOMWNtbDBlUzF3Y205a2RXTjBjeUlzSW1kcGRHeGhZaTF2Y21jdmMyVmpkWEpwZEhrdGNISnZaSFZqZEhNdllXNWhiSGw2WlhKeklsMTkuZ2Fwc01vcVJSOWZyVS1MQTVYaUtaMC1PYWVkNG1SSXNiOG5JbEJuVUswM1lTVTY4R2Y5WlhLV1F0VHMwbGpCekNxLWZhOXBWY0I5YU1TUnZ0bGJuZ0doNFU0aWpWUGU0am5vWC01VXJZaTJpTEYxdDJ5VGRFWEhDSWg2bXNBLXJEUTUwR2UxNUtaSmRXTE0tbFo0VGhNNENlbGpQMWF4NUJjeUV0UG1pcmZIaHppR3pKYmFEczRVMk5aaW1hcHo3Q1hSX3FaeHI0ajYyQW00dmVYXzhPaDFhT1I2bUtDMTlCZUlqeFozWlZ3Z0x3UUVsaFlLcEhUWTRSS2ZnUkh3TVlXVGZIZUF0VTM4UTV0VW9DSGU0RW1wcEIza0x0aW1Gemp2YWhnMGRjazBzc3FTWkh4X252cXJldjctSVdKa096OVRSVG04SU1xU3h4OUxxd1pCVFRRIn0=
```

When it is decoded it has the following form:

```json

	{
	  "access_token": "d746e5f0d66f25813fcc22c3cca4bf1b3f940249591a88c6f00c043e31a5dd4a",
	  "token_type": "Bearer",
	  "refresh_token": "2de151a0d6dba75905f4dfe4ee8d6d90ccfddc61b2d4de75684bc1cbf9b1480e",
	  "expiry": "2025-04-25T11:38:39.340047824-06:00",
	  "id_token": "eyJ0eXAiOiJKV1QiLCJraWQiOiJ0ZDBTbWRKUTRxUGg1cU5Lek0yNjBDWHgyVWgtd2hHLU1Eam9PS1dmdDhFIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMCIsInN1YiI6IjEiLCJhdWQiOiJlMzFlMWRhMGI4ZjZiNmUzNWNhNzBjNzkwYjEzYzA0MDZlNDRhY2E2YjJiZjY3ZjU1ZGU3MzU1YTk3OWEyMjRmIiwiZXhwIjoxNzQ1NTk1NjM5LCJpYXQiOjE3NDU1OTU1MTksImF1dGhfdGltZSI6MTc0NTUwMzMzOCwic3ViX2xlZ2FjeSI6IjI0NzRjZjBiMjIxMTY4OGE1NzI5N2FjZTBlMjYwYTE1OTQ0NzU0ZDE2YjFiZDQyYzlkNjc3OWM5MDAzNjc4MDciLCJuYW1lIjoiQWRtaW5pc3RyYXRvciIsIm5pY2tuYW1lIjoicm9vdCIsInByZWZlcnJlZF91c2VybmFtZSI6InJvb3QiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByb2ZpbGUiOiJodHRwOi8vZ2RrLnRlc3Q6MzAwMC9yb290IiwicGljdHVyZSI6Imh0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvMjU4ZDhkYzkxNmRiOGNlYTJjYWZiNmMzY2QwY2IwMjQ2ZWZlMDYxNDIxZGJkODNlYzNhMzUwNDI4Y2FiZGE0Zj9zPTgwJmQ9aWRlbnRpY29uIiwiZ3JvdXBzX2RpcmVjdCI6WyJnaXRsYWItb3JnIiwidG9vbGJveCIsIm1hc3NfaW5zZXJ0X2dyb3VwX18wXzEwMCIsImN1c3RvbS1yb2xlcy1yb290LWdyb3VwL2FhIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAvYWEvYWFhIiwiZ251d2dldCIsIkNvbW1pdDQ1MSIsImphc2hrZW5hcyIsImZsaWdodGpzIiwidHdpdHRlciIsImdpdGxhYi1leGFtcGxlcyIsImdpdGxhYi1leGFtcGxlcy9zZWN1cml0eSIsIjQxMjcwOCIsImdpdGxhYi1leGFtcGxlcy9kZW1vLWdyb3VwIiwiY3VzdG9tLXJvbGVzLXJvb3QtZ3JvdXAiLCI0MzQwNDQtZ3JvdXAtMSIsIjQzNDA0NC1ncm91cC0yIiwiZ2l0bGFiLW9yZzEiLCJnaXRsYWItb3JnL3NlY3VyZSIsImdpdGxhYi1vcmcvc2VjdXJlL21hbmFnZXJzIiwiZ2l0bGFiLW9yZy9zZWN1cml0eS1wcm9kdWN0cyIsImdpdGxhYi1vcmcvc2VjdXJpdHktcHJvZHVjdHMvYW5hbHl6ZXJzIl19.gapsMoqRR9frU-LA5XiKZ0-Oaed4mRIsb8nIlBnUK03YSU68Gf9ZXKWQtTs0ljBzCq-fa9pVcB9aMSRvtlbngGh4U4ijVPe4jnoX-5UrYi2iLF1t2yTdEXHCIh6msA-rDQ50Ge15KZJdWLM-lZ4ThM4CeljP1ax5BcyEtPmirfHhziGzJbaDs4U2NZimapz7CXR_qZxr4j62Am4veX_8Oh1aOR6mKC19BeIjxZ3ZVwgLwQElhYKpHTY4RKfgRHwMYWTfHeAtU38Q5tUoCHe4EmppB3kLtimFzjvahg0dck0ssqSZHx_nvqrev7-IWJkOz9TRTm8IMqSxx9LqwZBTTQ"
	}

```
*/
func (c *Controller) Create(w http.ResponseWriter, r *http.Request) {
	tokens, err := c.svc.Exchange(r)
	if err != nil {
		pls.LogError(r.Context(), err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	encoded, err := tokens.ToBase64String()
	if err != nil {
		pls.LogError(r.Context(), err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	web.ExpireCookie(w, cfg.CSRFCookie)
	web.WriteCookie(w, web.NewCookie(cfg.SessionCookie, encoded))

	c.redirectTo(w, r, "/dashboard")
}

func (c *Controller) Destroy(w http.ResponseWriter, r *http.Request) {
	web.ExpireCookie(w, cfg.CSRFCookie)
	web.ExpireCookie(w, cfg.SessionCookie)
	c.redirectTo(w, r, "/")
}

func (c *Controller) redirectTo(w http.ResponseWriter, r *http.Request, location string) {
	if env.Fetch("APP_ENV", "development") == "production" {
		(&RedirectDTO{URL: location}).ServeHTTP(w, r)
	} else {
		http.Redirect(w, r, location, http.StatusFound)
	}
}