package domain import ( "crypto/hmac" "crypto/sha256" "crypto/subtle" "encoding/base64" "encoding/json" "errors" "strings" "time" "git.cin.su/m/Assistant/contracts/identity" ) var ErrInvalidCreatorSession = errors.New("invalid creator session") func IssueCreatorSession(secret []byte, now time.Time, ttl time.Duration) (string, identity.CreatorSession, error) { return IssueCreatorSessionForBinding(secret, identity.ActorID("domovoy-creator-actor"), identity.PrincipalID("domovoy-creator-principal"), now, ttl) } func IssueCreatorSessionForBinding(secret []byte, actorID identity.ActorID, principalID identity.PrincipalID, now time.Time, ttl time.Duration) (string, identity.CreatorSession, error) { if len(secret) < 32 || actorID == "" || principalID == "" || ttl <= 0 { return "", identity.CreatorSession{}, ErrInvalidCreatorSession } session := identity.CreatorSession{ActorID: actorID, PrincipalID: principalID, ExpiresAtUnix: now.Add(ttl).Unix()} payload, err := json.Marshal(session) if err != nil { return "", identity.CreatorSession{}, err } encoded := base64.RawURLEncoding.EncodeToString(payload) return encoded + "." + signature(secret, encoded), session, nil } func ValidateCreatorSession(secret []byte, token string, now time.Time) (identity.CreatorSession, error) { parts := strings.Split(token, ".") if len(parts) != 2 || len(secret) < 32 || subtle.ConstantTimeCompare([]byte(signature(secret, parts[0])), []byte(parts[1])) != 1 { return identity.CreatorSession{}, ErrInvalidCreatorSession } payload, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { return identity.CreatorSession{}, ErrInvalidCreatorSession } var session identity.CreatorSession if json.Unmarshal(payload, &session) != nil || session.ActorID == "" || session.PrincipalID == "" || now.Unix() >= session.ExpiresAtUnix { return identity.CreatorSession{}, ErrInvalidCreatorSession } return session, nil } func BootstrapMatches(expected, supplied string) bool { return expected != "" && supplied != "" && subtle.ConstantTimeCompare([]byte(expected), []byte(supplied)) == 1 } func CreatorCredentialsMatch(expectedUsername, expectedPassword string, supplied identity.CreatorCredentials) bool { if expectedUsername == "" || expectedPassword == "" || supplied.Username == "" || supplied.Password == "" { return false } usernameMatches := subtle.ConstantTimeCompare([]byte(expectedUsername), []byte(supplied.Username)) passwordMatches := subtle.ConstantTimeCompare([]byte(expectedPassword), []byte(supplied.Password)) return usernameMatches&passwordMatches == 1 } func signature(secret []byte, payload string) string { mac := hmac.New(sha256.New, secret) mac.Write([]byte(payload)) return base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) }