summaryrefslogtreecommitdiff
path: root/app/models/oauth2.go
blob: 9bfde0a13f191c7bd2ce15304daec0c456d0e361 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package models

import (
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"fmt"
)

const (
	DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
)

type AuthorizationRequest struct {
	Challenge       string            `url:"code_challenge" form:"code_challenge" json:"code_challenge"`                      // RFC7636
	ChallengeMethod PKCEChallengeType `url:"code_challenge_method" form:"code_challenge_method" json:"code_challenge_method"` // RFC7636
	ClientId        string            `url:"client_id" form:"client_id" json:"client_id"`
	Scope           string            `url:"scope" form:"scope" json:"scope"`
}

type DeviceAuthorizationResponse struct {
	DeviceCode              string `json:"device_code"`      // REQUIRED
	UserCode                string `json:"user_code"`        // REQUIRED
	VerificationUri         string `json:"verification_uri"` // REQUIRED
	VerificationUriComplete string `json:"verification_uri_complete,omitempty"`
	ExpiresIn               int    `json:"expires_in,omitempty"`
	Interval                int    `json:"interval,omitempty"`
}

type DeviceAccessTokenRequest struct {
	GrantType    string `url:"grant_type" form:"grant_type" json:"grant_type"`
	DeviceCode   string `url:"device_code" form:"device_code" json:"device_code"`
	ClientId     string `url:"client_id" form:"client_id" json:"client_id"`
	CodeVerifier string `url:"code_verifier" form:"code_verifier" json:"code_verifier"`
}

type AccessTokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`           // Must be Bearer
	ExpiresIn    string `json:"expires_in,omitempty"` // Lifetime in seconds
	RefreshToken string `json:"refresh_token,omitempty"`
	Scope        string `json:"scope,omitempty"`
}

type AuthorizationError string

const (
	ErrInvalidRequest       AuthorizationError = "invalid_request"
	ErrInvalidClient                           = "invalid_client"
	ErrInvalidGrant                            = "invalid_grant"
	ErrUnauthorizedClient                      = "unauthorized_client"
	ErrUnsupportedGrantType                    = "unsupported_grant_type"
	ErrInvalidScope                            = "invalid_scope"
	ErrAuthorizationPending                    = "authorization_pending" // RFC7636
	ErrSlowDown                                = "slow_down"             // RFC7636
	ErrAccessDenied                            = "access_denied"         // RFC7636
	ErrExpiredToken                            = "expired_token"         // RFC7636
)

type Oauth2Error struct {
	Type        AuthorizationError `json:"error"`
	Description string             `json:"error_description,omitempty"`
	Uri         string             `json:"error_uri,omitempty"`
}

func (e Oauth2Error) Error() string {
	if e.Description == "" {
		return fmt.Sprintf("Oauth2Error: %s", e.Type)
	} else {
		return fmt.Sprintf("Oauth2Error: %s %s", e.Type, e.Description)
	}
}

type PKCEChallengeType string

const (
	ChallengePlain PKCEChallengeType = "plain"
	ChallengeS256                    = "S256"
)

type PKCEChallenge struct {
	Verifier string
}

func NewPKCEChallenge() (*PKCEChallenge, error) {
	buf := make([]byte, 32)
	if _, err := rand.Read(buf); err != nil {
		return nil, err
	}
	return &PKCEChallenge{
		Verifier: base64.URLEncoding.EncodeToString(buf),
	}, nil
}

func (c *PKCEChallenge) Challenge() string {
	hash := sha256.Sum256([]byte(c.Verifier))
	return base64.URLEncoding.EncodeToString(hash[:])
}

func (c *PKCEChallenge) EqualString(o string) bool {
	return subtle.ConstantTimeCompare([]byte(o), []byte(c.Challenge())) != 1
}