Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol diffdown.com
at main 114 lines 2.6 kB view raw
1package auth 2 3import ( 4 "context" 5 "crypto/rand" 6 "crypto/sha256" 7 "encoding/base64" 8 "encoding/hex" 9 "fmt" 10 "net/http" 11 12 "github.com/gorilla/sessions" 13 "golang.org/x/crypto/bcrypt" 14) 15 16type contextKey string 17 18const UserIDKey contextKey = "user_id" 19 20var store *sessions.CookieStore 21 22func InitStore(secret string) { 23 store = sessions.NewCookieStore([]byte(secret)) 24 store.Options = &sessions.Options{ 25 Path: "/", 26 MaxAge: 86400 * 30, // 30 days 27 HttpOnly: true, 28 SameSite: http.SameSiteLaxMode, 29 } 30} 31 32func GetSession(r *http.Request) *sessions.Session { 33 sess, _ := store.Get(r, "mdhub") 34 return sess 35} 36 37func SetUserID(w http.ResponseWriter, r *http.Request, userID string) error { 38 sess := GetSession(r) 39 sess.Values["user_id"] = userID 40 return sess.Save(r, w) 41} 42 43func GetUserID(r *http.Request) string { 44 sess := GetSession(r) 45 if v, ok := sess.Values["user_id"].(string); ok { 46 return v 47 } 48 return "" 49} 50 51func ClearSession(w http.ResponseWriter, r *http.Request) error { 52 sess := GetSession(r) 53 sess.Options.MaxAge = -1 54 return sess.Save(r, w) 55} 56 57func UserIDFromContext(ctx context.Context) string { 58 if v, ok := ctx.Value(UserIDKey).(string); ok { 59 return v 60 } 61 return "" 62} 63 64// Password helpers 65 66func HashPassword(password string) (string, error) { 67 hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 68 if err != nil { 69 return "", err 70 } 71 return string(hash), nil 72} 73 74func CheckPassword(hash, password string) bool { 75 return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil 76} 77 78func GenerateToken() string { 79 b := make([]byte, 32) 80 rand.Read(b) 81 return hex.EncodeToString(b) 82} 83 84// State parameter for CSRF protection 85func SetOAuthState(w http.ResponseWriter, r *http.Request) string { 86 state := GenerateToken() 87 sess := GetSession(r) 88 sess.Values["oauth_state"] = state 89 sess.Save(r, w) 90 return state 91} 92 93func ValidateOAuthState(r *http.Request, state string) error { 94 sess := GetSession(r) 95 expected, ok := sess.Values["oauth_state"].(string) 96 if !ok || expected != state { 97 return fmt.Errorf("invalid oauth state") 98 } 99 delete(sess.Values, "oauth_state") 100 return nil 101} 102 103// PKCEVerifier generates a cryptographically random PKCE code verifier (43-128 chars, URL-safe base64). 104func PKCEVerifier() string { 105 b := make([]byte, 32) 106 rand.Read(b) 107 return base64.RawURLEncoding.EncodeToString(b) 108} 109 110// PKCEChallenge derives the S256 code challenge from a verifier. 111func PKCEChallenge(verifier string) string { 112 h := sha256.Sum256([]byte(verifier)) 113 return base64.RawURLEncoding.EncodeToString(h[:]) 114}