159 lines
4.6 KiB
Go
159 lines
4.6 KiB
Go
package shared
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"galaxy/authsession/internal/domain/common"
|
|
"galaxy/authsession/internal/domain/devicesession"
|
|
)
|
|
|
|
// NormalizeString trims surrounding Unicode whitespace from value.
|
|
func NormalizeString(value string) string {
|
|
return strings.TrimSpace(value)
|
|
}
|
|
|
|
// ParseEmail trims value and validates it against the frozen public e-mail
|
|
// contract.
|
|
func ParseEmail(value string) (common.Email, error) {
|
|
email := common.Email(NormalizeString(value))
|
|
if err := email.Validate(); err != nil {
|
|
return "", InvalidRequest(err.Error())
|
|
}
|
|
|
|
return email, nil
|
|
}
|
|
|
|
// ParseChallengeID trims value and validates it as one challenge identifier.
|
|
func ParseChallengeID(value string) (common.ChallengeID, error) {
|
|
challengeID := common.ChallengeID(NormalizeString(value))
|
|
if err := challengeID.Validate(); err != nil {
|
|
return "", InvalidRequest(err.Error())
|
|
}
|
|
|
|
return challengeID, nil
|
|
}
|
|
|
|
// ParseDeviceSessionID trims value and validates it as one device-session
|
|
// identifier.
|
|
func ParseDeviceSessionID(value string) (common.DeviceSessionID, error) {
|
|
deviceSessionID := common.DeviceSessionID(NormalizeString(value))
|
|
if err := deviceSessionID.Validate(); err != nil {
|
|
return "", InvalidRequest(err.Error())
|
|
}
|
|
|
|
return deviceSessionID, nil
|
|
}
|
|
|
|
// ParseUserID trims value and validates it as one user identifier.
|
|
func ParseUserID(value string) (common.UserID, error) {
|
|
userID := common.UserID(NormalizeString(value))
|
|
if err := userID.Validate(); err != nil {
|
|
return "", InvalidRequest(err.Error())
|
|
}
|
|
|
|
return userID, nil
|
|
}
|
|
|
|
// ParseRequiredCode trims value and validates it as a required non-empty
|
|
// confirmation code.
|
|
func ParseRequiredCode(value string) (string, error) {
|
|
code := NormalizeString(value)
|
|
if code == "" {
|
|
return "", InvalidRequest("code must not be empty")
|
|
}
|
|
|
|
return code, nil
|
|
}
|
|
|
|
// ParseClientPublicKey trims value and validates it as the standard
|
|
// base64-encoded raw 32-byte Ed25519 public key expected by the public auth
|
|
// contract.
|
|
func ParseClientPublicKey(value string) (common.ClientPublicKey, error) {
|
|
normalized := NormalizeString(value)
|
|
if normalized == "" {
|
|
return common.ClientPublicKey{}, InvalidClientPublicKey()
|
|
}
|
|
|
|
decoded, err := base64.StdEncoding.Strict().DecodeString(normalized)
|
|
if err != nil || len(decoded) != ed25519.PublicKeySize {
|
|
return common.ClientPublicKey{}, InvalidClientPublicKey()
|
|
}
|
|
|
|
key, err := common.NewClientPublicKey(ed25519.PublicKey(decoded))
|
|
if err != nil {
|
|
return common.ClientPublicKey{}, InvalidClientPublicKey()
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
// ParseRevokeReasonCode trims value and validates it as one machine-readable
|
|
// revoke reason code.
|
|
func ParseRevokeReasonCode(value string) (common.RevokeReasonCode, error) {
|
|
code := common.RevokeReasonCode(NormalizeString(value))
|
|
if err := code.Validate(); err != nil {
|
|
return "", InvalidRequest(err.Error())
|
|
}
|
|
|
|
return code, nil
|
|
}
|
|
|
|
// ParseRevokeActorType trims value and validates it as one machine-readable
|
|
// revoke actor type.
|
|
func ParseRevokeActorType(value string) (common.RevokeActorType, error) {
|
|
actorType := common.RevokeActorType(NormalizeString(value))
|
|
if err := actorType.Validate(); err != nil {
|
|
return "", InvalidRequest(err.Error())
|
|
}
|
|
|
|
return actorType, nil
|
|
}
|
|
|
|
// ParseOptionalActorID trims value and validates it as one optional stable
|
|
// actor identifier.
|
|
func ParseOptionalActorID(value string) (string, error) {
|
|
actorID := NormalizeString(value)
|
|
if actorID != value {
|
|
return "", InvalidRequest("actor_id must not contain surrounding whitespace")
|
|
}
|
|
|
|
return actorID, nil
|
|
}
|
|
|
|
// BuildRevocation validates one revoke request payload and returns the domain
|
|
// revocation metadata applied to a session mutation.
|
|
func BuildRevocation(reasonCode string, actorType string, actorID string, at time.Time) (devicesession.Revocation, error) {
|
|
if at.IsZero() {
|
|
return devicesession.Revocation{}, InternalError(fmt.Errorf("revocation time must not be zero"))
|
|
}
|
|
|
|
parsedReasonCode, err := ParseRevokeReasonCode(reasonCode)
|
|
if err != nil {
|
|
return devicesession.Revocation{}, err
|
|
}
|
|
parsedActorType, err := ParseRevokeActorType(actorType)
|
|
if err != nil {
|
|
return devicesession.Revocation{}, err
|
|
}
|
|
parsedActorID, err := ParseOptionalActorID(actorID)
|
|
if err != nil {
|
|
return devicesession.Revocation{}, err
|
|
}
|
|
|
|
revocation := devicesession.Revocation{
|
|
At: at.UTC(),
|
|
ReasonCode: parsedReasonCode,
|
|
ActorType: parsedActorType,
|
|
ActorID: parsedActorID,
|
|
}
|
|
if err := revocation.Validate(); err != nil {
|
|
return devicesession.Revocation{}, InternalError(fmt.Errorf("build revocation: %w", err))
|
|
}
|
|
|
|
return revocation, nil
|
|
}
|