mirror of
https://github.com/xslasd/x-oidc.git
synced 2025-10-16 13:00:52 +08:00
138 lines
4.1 KiB
Go
138 lines
4.1 KiB
Go
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/xslasd/x-oidc/ecode"
|
|
"time"
|
|
)
|
|
|
|
// TokenClaims contains the base Claims used all tokens.
|
|
// It implements OpenID Connect Core 1.0, section 2.
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
|
// And RFC 9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens,
|
|
// section 2.2. https://datatracker.ietf.org/doc/html/rfc9068#name-data-structure
|
|
//
|
|
// TokenClaims implements the Claims interface,
|
|
// and can be used to extend larger claim types by embedding.
|
|
type TokenClaims struct {
|
|
Issuer string `json:"iss,omitempty"`
|
|
Subject string `json:"sub,omitempty"`
|
|
Audience Audience `json:"aud,omitempty"`
|
|
Expiration int64 `json:"exp,omitempty"`
|
|
IssuedAt int64 `json:"iat,omitempty"`
|
|
NotBefore int64 `json:"nbf,omitempty"`
|
|
Nonce string `json:"nonce,omitempty"`
|
|
AuthenticationContextClassReference string `json:"acr,omitempty"`
|
|
AuthenticationMethodsReferences []string `json:"amr,omitempty"`
|
|
AuthorizedParty string `json:"azp,omitempty"`
|
|
ClientID string `json:"client_id,omitempty"`
|
|
JWTID string `json:"jti,omitempty"`
|
|
}
|
|
|
|
func (t *TokenClaims) CheckIssuer(issuer string) error {
|
|
if t.Issuer != issuer {
|
|
return ecode.CheckIssuerInvalid
|
|
}
|
|
return nil
|
|
}
|
|
func (t *TokenClaims) CheckAudience(audience string) error {
|
|
for _, item := range t.Audience {
|
|
if item == audience {
|
|
return nil
|
|
}
|
|
}
|
|
return ecode.AudienceInvalid.SetDescriptionf(audience)
|
|
}
|
|
|
|
func (t *TokenClaims) CheckExpiration() error {
|
|
t1 := time.Unix(t.Expiration, 0).UTC().Round(time.Second)
|
|
now := time.Now().UTC().Round(time.Second)
|
|
fmt.Println("CheckExpiration:", t1, now)
|
|
if !now.Before(t1) {
|
|
return ecode.TokenExpired
|
|
}
|
|
return nil
|
|
}
|
|
func (t *TokenClaims) CheckIssuedAt() error {
|
|
issuedAt := time.Unix(t.IssuedAt, 0).UTC().Round(time.Second)
|
|
now := time.Now().UTC().Round(time.Second)
|
|
fmt.Println("CheckIssuedAt:", issuedAt, now)
|
|
if now.Before(issuedAt) {
|
|
return ecode.TokenIssuedAtInvalid
|
|
}
|
|
return nil
|
|
}
|
|
func (t *TokenClaims) CheckNonce(nonce string) error {
|
|
if t.Nonce != nonce {
|
|
return ecode.TokenNonceInvalid
|
|
}
|
|
return nil
|
|
}
|
|
func (t *TokenClaims) CheckAuthorizationContextClassReference(acr string) error {
|
|
if t.AuthenticationContextClassReference != acr {
|
|
return ecode.TokenACRInvalid
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type JWTClientTokenClaims struct {
|
|
Issuer string `json:"iss"`
|
|
Subject string `json:"sub"`
|
|
Audience Audience `json:"aud"`
|
|
IssuedAt int64 `json:"iat"`
|
|
ExpiresAt int64 `json:"exp"`
|
|
|
|
Scopes string `json:"-"`
|
|
}
|
|
|
|
type AccessTokenClaims struct {
|
|
TokenClaims
|
|
Scopes string `json:"scope,omitempty"` //Then Scopes apace delimited array
|
|
//Claims map[string]any `json:"-"`
|
|
}
|
|
|
|
// IDTokenClaims extends TokenClaims by further implementing
|
|
// OpenID Connect Core 1.0, sections 3.1.3.6 (Code flow),
|
|
// 3.2.2.10 (implicit), 3.3.2.11 (Hybrid) and 5.1 (UserInfo).
|
|
// https://openid.net/specs/openid-connect-core-1_0.html#toc
|
|
type IDTokenClaims struct {
|
|
TokenClaims
|
|
AuthTime int64 `json:"auth_time,omitempty"`
|
|
AccessTokenHash string `json:"at_hash,omitempty"`
|
|
CodeHash string `json:"c_hash,omitempty"`
|
|
SessionID string `json:"sid,omitempty"`
|
|
UserInfoProfile
|
|
UserInfoEmail
|
|
UserInfoPhone
|
|
Address *UserInfoAddress `json:"address,omitempty"`
|
|
// Claims map[string]any `json:"-"`
|
|
}
|
|
|
|
func (s *IDTokenClaims) SetUserInfo(i *UserInfo) {
|
|
s.UserInfoProfile = i.UserInfoProfile
|
|
s.UserInfoEmail = i.UserInfoEmail
|
|
s.UserInfoPhone = i.UserInfoPhone
|
|
s.Address = i.Address
|
|
}
|
|
|
|
type Audience []string
|
|
|
|
func (a *Audience) UnmarshalJSON(text []byte) error {
|
|
var i interface{}
|
|
err := json.Unmarshal(text, &i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch aud := i.(type) {
|
|
case []interface{}:
|
|
*a = make([]string, len(aud))
|
|
for i, audience := range aud {
|
|
(*a)[i] = audience.(string)
|
|
}
|
|
case string:
|
|
*a = []string{aud}
|
|
}
|
|
return nil
|
|
}
|