1package oidcrp
2
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/base64"
7 "encoding/json"
8 "net/http"
9 "time"
10)
11
12const LoginCookie = "__Host-authsvc_login"
13const LoginInfo = "tug login v1"
14const LoginProofLength = 32
15const LoginProofInfo = "tug login proof v1"
16
17type loginCookie struct {
18 Proof string `json:"p"`
19 Expiry int64 `json:"x"`
20}
21
22func (rp *RP) decodeLogin(name, value string) (loginCookie, bool) {
23 raw, err := rp.login.Open(name, value)
24 if err != nil {
25 return loginCookie{}, false
26 }
27 var c loginCookie
28 if err := json.Unmarshal(raw, &c); err != nil {
29 return loginCookie{}, false
30 }
31 proof, err := base64.RawURLEncoding.Strict().DecodeString(c.Proof)
32 return c, err == nil && len(proof) == LoginProofLength && rp.now().Before(time.UnixMicro(c.Expiry))
33}
34
35func (rp *RP) LoginProof(r *http.Request, state string) string {
36 name := cookieName(LoginCookie, state)
37 ck, err := r.Cookie(name)
38 if err != nil {
39 return ""
40 }
41 c, ok := rp.decodeLogin(name, ck.Value)
42 if !ok {
43 return ""
44 }
45 return c.Proof
46}
47
48func (rp *RP) ClearLogin(w http.ResponseWriter, state string) {
49 http.SetCookie(w, cookie(cookieName(LoginCookie, state), "", -1))
50}
51
52func (rp *RP) LoginProofFor(state, browser string) string {
53 mac := hmac.New(sha256.New, rp.proofKey)
54 mac.Write([]byte(state))
55 mac.Write([]byte{0})
56 mac.Write([]byte(browser))
57 return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
58}
59
60func (rp *RP) SetLogin(w http.ResponseWriter, r *http.Request, state, proof string, expiry time.Time, evictLive bool) bool {
61 now := rp.now()
62 if limit := now.Add(time.Hour); expiry.After(limit) {
63 expiry = limit
64 }
65 name := cookieName(LoginCookie, state)
66 maxAge := int(expiry.Sub(now) / time.Second)
67 if maxAge <= 0 {
68 http.SetCookie(w, cookie(name, "", -1))
69 return false
70 }
71 live := liveCookies(w, r, LoginCookie+"_", now, func(c *http.Cookie) (time.Time, bool) {
72 held, ok := rp.decodeLogin(c.Name, c.Value)
73 return time.UnixMicro(held.Expiry), ok
74 })
75 for evictLive && len(live) >= 4 {
76 http.SetCookie(w, cookie(live[0].name, "", -1))
77 live = live[1:]
78 }
79 raw, _ := json.Marshal(loginCookie{Proof: proof, Expiry: expiry.UnixMicro()})
80 http.SetCookie(w, cookie(name, rp.login.Seal(name, raw), maxAge))
81 return true
82}