oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/base64"
7 "encoding/hex"
8 "net/http"
9 "strconv"
10 "strings"
11 "time"
12)
13
14const joinCookieName = "calls_join"
15
16// joinCookie tells POST /signin which live link this browser followed.
17// It carries the token's hash, not the token, so a cookie leak is not a
18// link leak, and it is signed under the instance key so it cannot be
19// forged. Its only job is that one hand-off (spec §3.2).
20type joinCookie struct {
21 TokenHash string
22 Expires time.Time
23}
24
25func (a *app) joinMAC(payload string) string {
26 mac := hmac.New(sha256.New, []byte("calls/join\x00"+a.cfg.instanceKey))
27 mac.Write([]byte(payload))
28 return hex.EncodeToString(mac.Sum(nil))
29}
30
31func (a *app) setJoinCookie(w http.ResponseWriter, tokenHash string) {
32 exp := a.store.now().Add(pendingLife)
33 payload := tokenHash + "|" + strconv.FormatInt(exp.Unix(), 10)
34 value := base64.RawURLEncoding.EncodeToString([]byte(payload)) + "." + a.joinMAC(payload)
35 http.SetCookie(w, &http.Cookie{
36 Name: joinCookieName, Value: value, Path: "/", HttpOnly: true,
37 Secure: strings.HasPrefix(a.cfg.Origin, "https://"), SameSite: http.SameSiteLaxMode,
38 MaxAge: int(pendingLife.Seconds()),
39 })
40}
41
42func (a *app) clearJoinCookie(w http.ResponseWriter) {
43 http.SetCookie(w, &http.Cookie{Name: joinCookieName, Value: "", Path: "/", MaxAge: -1, HttpOnly: true})
44}
45
46func (a *app) readJoinCookie(r *http.Request) (joinCookie, bool) {
47 ck, err := r.Cookie(joinCookieName)
48 if err != nil {
49 return joinCookie{}, false
50 }
51 parts := strings.SplitN(ck.Value, ".", 2)
52 if len(parts) != 2 {
53 return joinCookie{}, false
54 }
55 raw, err := base64.RawURLEncoding.DecodeString(parts[0])
56 if err != nil {
57 return joinCookie{}, false
58 }
59 payload := string(raw)
60 if !hmac.Equal([]byte(a.joinMAC(payload)), []byte(parts[1])) {
61 return joinCookie{}, false
62 }
63 hash, expStr, ok := strings.Cut(payload, "|")
64 if !ok || len(hash) != 64 {
65 return joinCookie{}, false
66 }
67 exp, err := strconv.ParseInt(expStr, 10, 64)
68 if err != nil {
69 return joinCookie{}, false
70 }
71 c := joinCookie{TokenHash: hash, Expires: time.Unix(exp, 0)}
72 if !c.Expires.After(a.store.now()) {
73 return joinCookie{}, false
74 }
75 return c, true
76}
77