oidc

package
v0.12.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package oidc implements login with external identity providers (GitHub, Google, generic OpenID Connect with discovery) for both the RustDesk client (/api/oidc/*, polling) and the panel (cookie session), plus linking identities to existing accounts.

Security properties:

  • state is a 256-bit random value stored only as sha256 in oauth_states, 5 minute TTL, single use (atomic claim/consume);
  • PKCE (S256 by default) with the verifier AEAD-encrypted at rest;
  • a nonce bound into the ID token; ID tokens are verified (asymmetric algorithms only, iss/aud/azp/exp/iat/nonce);
  • the redirect URI is always <public_url>/api/oidc/callback, never derived from request headers;
  • client polls are bound to the requesting device id+uuid;
  • panel flows are bound to the browser by an HttpOnly cookie (only its sha256 is stored on the state row). The IdP returns to the public host; the callback stores the result and hands off to the admin host with a single-use ticket (HandoffTTL), where the cookie is verified before any session is created or identity linked (login CSRF protection, works when the panel and the client API are served from different hosts);
  • an identity is linked to an existing user by email only when the IdP asserts email_verified=true and the provider enables link_by_verified_email.

Index

Constants

View Source
const (
	// PendingError keeps the client polling (substring match on the client side).
	PendingError = "No authed oidc is found"
	// InvalidCodeError ends the attempt (unknown, expired, used or foreign code).
	InvalidCodeError = "Invalid or expired code"
)

Exact wire strings of the RustDesk client contract (docs/research/client-api.md §9).

View Source
const BindingCookie = "rdpanel_oauth"

BindingCookie binds a panel flow to the browser that started it.

View Source
const CallbackPath = "/api/oidc/callback"

CallbackPath is the redirect URI path registered at every IdP (legacy compatible: production IdP registrations contain exactly this path).

View Source
const DefaultBindReturn = "/my/profile"

DefaultBindReturn is where the browser returns after linking an identity.

View Source
const DefaultStateTTL = 5 * time.Minute

DefaultStateTTL is the lifetime of a pending OAuth flow.

View Source
const GoogleIssuer = "https://accounts.google.com"

GoogleIssuer is the fixed issuer of kind "google" providers.

View Source
const HandoffPath = "/api/v1/admin/auth/oidc/complete"

HandoffPath is the admin-host endpoint completing panel flows.

View Source
const HandoffTTL = 2 * time.Minute

HandoffTTL bounds the hand-off ticket of a panel flow (callback on the public host → GET /api/v1/admin/auth/oidc/complete on the admin host).

View Source
const LoginErrorPath = "/login"

LoginErrorPath is the SPA route panel OIDC failures redirect to.

Variables

View Source
var (
	ErrUnknownProvider = errors.New("unknown or disabled identity provider")
	ErrNotConfigured   = errors.New("OIDC is not available: public URL is not configured")
	// ErrProviderUnavailable means discovery or secret decryption failed.
	ErrProviderUnavailable = errors.New("identity provider is unavailable")
)

Errors returned to callers (messages are safe to show).

View Source
var DefaultGitHub = GitHubEndpoints{
	AuthURL:  "https://github.com/login/oauth/authorize",
	TokenURL: "https://github.com/login/oauth/access_token",
	APIURL:   "https://api.github.com",
}

DefaultGitHub are the public github.com endpoints.

Functions

func ClientUserStatus

func ClientUserStatus(u *store.User) int

ClientUserStatus maps a user status to the client enum (1 normal, 0 disabled).

func Register

func Register(r chi.Router, d RouteDeps)

Register mounts the RustDesk client OIDC endpoints on r (already scoped to /api). Stream A's clientapi must not register these paths:

GET  /login-options
POST /oidc/auth
GET  /oidc/auth-query
GET  /oidc/callback   (+ legacy alias /oauth/callback)

func SafeRedirectPath

func SafeRedirectPath(p, def string) string

SafeRedirectPath returns p when it is a same-origin relative path ("/x?y#z", not "//host", "/\host" or anything with a scheme), else def.

func SupportedKind

func SupportedKind(k store.ProviderKind) bool

SupportedKind reports whether kind can be used for logins.

Types

type AuthBody

type AuthBody struct {
	AccessToken string      `json:"access_token"`
	Type        string      `json:"type"`
	User        UserPayload `json:"user"`
}

AuthBody is the auth-query success body.

type ClientDevice

type ClientDevice struct {
	ID, UUID, Name, OS, Type string
}

ClientDevice identifies the RustDesk device starting a client flow.

type ClientLoginFunc

type ClientLoginFunc func(ctx context.Context, user *store.User, tokenID int64, st *store.OAuthState)

ClientLoginFunc is called after a client token was issued via OIDC (e.g. to bind the device to the user). Optional.

type GitHubEndpoints

type GitHubEndpoints struct {
	AuthURL  string
	TokenURL string
	APIURL   string // base for /user and /user/emails
}

GitHubEndpoints are the OAuth endpoints of GitHub (overridable in tests).

type Metadata

type Metadata struct {
	Issuer                   string   `json:"issuer"`
	AuthorizationEndpoint    string   `json:"authorization_endpoint"`
	TokenEndpoint            string   `json:"token_endpoint"`
	UserinfoEndpoint         string   `json:"userinfo_endpoint,omitempty"`
	JWKSURI                  string   `json:"jwks_uri"`
	TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported,omitempty"`
	CodeChallengeMethods     []string `json:"code_challenge_methods_supported,omitempty"`
}

Metadata is the subset of an OpenID Provider configuration we use.

type Options

type Options struct {
	Store *store.Store
	AEAD  *crypto.AEAD
	JWT   *auth.ClientJWT
	Log   *slog.Logger
	// PublicURL is the configured external base URL (no trailing slash).
	PublicURL string
	// AdminURL is the panel base URL (default PublicURL); panel flows are
	// handed off there after the IdP callback.
	AdminURL string
	// TokenLifetime is the sliding lifetime of client tokens; JWTLifetime the
	// fixed JWT exp lifetime (0 = TokenLifetime).
	TokenLifetime time.Duration
	JWTLifetime   time.Duration
	// StateTTL defaults to DefaultStateTTL.
	StateTTL time.Duration
	// HTTPClient talks to identity providers (default: 10 s timeout).
	HTTPClient *http.Client
	// SecureCookies sets the Secure attribute on the browser-binding cookie.
	SecureCookies bool
	WebLogin      WebLoginFunc
	OnClientLogin ClientLoginFunc
	// LoginEvents records login attempts (default: loginlog.DB on the store).
	LoginEvents loginlog.Recorder
	// Telemetry counts OIDC logins and issued client tokens (nil: none).
	Telemetry *metrics.Panel
	// GitHub overrides the GitHub endpoints (tests); GoogleIssuer the Google issuer.
	GitHub       *GitHubEndpoints
	GoogleIssuer string
}

Options configure a Service.

type Profile

type Profile struct {
	Subject       string `json:"subject"`
	Email         string `json:"email,omitempty"`
	EmailVerified bool   `json:"email_verified"`
	Username      string `json:"username,omitempty"`
	DisplayName   string `json:"display_name,omitempty"`
	AvatarURL     string `json:"avatar_url,omitempty"`
}

Profile is the identity asserted by a provider after a successful login.

type RouteDeps

type RouteDeps struct {
	Service *Service
	Log     *slog.Logger
}

RouteDeps are the collaborators of the client OIDC routes.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service runs OAuth/OIDC flows. Safe for concurrent use.

func NewService

func NewService(o Options) *Service

NewService builds a Service.

func (*Service) BeginBind

func (s *Service) BeginBind(ctx context.Context, w http.ResponseWriter, slug string, userID int64, next string) (string, error)

BeginBind starts linking an identity of provider slug to userID.

func (*Service) BeginClient

func (s *Service) BeginClient(ctx context.Context, slug string, dev ClientDevice) (code, authURL string, err error)

BeginClient starts a RustDesk client login (POST /api/oidc/auth). The returned code is the poll code (= the OAuth state).

func (*Service) BeginWebLogin

func (s *Service) BeginWebLogin(ctx context.Context, w http.ResponseWriter, slug, next string) (string, error)

BeginWebLogin starts a panel login and sets the browser-binding cookie. It returns the IdP URL the browser must navigate to.

func (*Service) CallbackURL

func (s *Service) CallbackURL() string

CallbackURL returns the redirect URI admins must register at the IdP ("" when the public URL is not configured).

func (*Service) CompleteWeb

func (s *Service) CompleteWeb(w http.ResponseWriter, r *http.Request)

CompleteWeb serves GET /api/v1/admin/auth/oidc/complete?ticket=… on the admin host: it consumes the single-use ticket, verifies the browser-binding cookie against the flow (constant time) and then signs the user in or links the identity, redirecting into the SPA.

func (*Service) Discover

func (s *Service) Discover(ctx context.Context, p *store.IdentityProvider) (*Metadata, error)

Discover runs (uncached) discovery for a provider row: the admin "test" action.

func (*Service) HandleCallback

func (s *Service) HandleCallback(w http.ResponseWriter, r *http.Request)

HandleCallback serves GET /api/oidc/callback (the IdP redirect target).

func (*Service) LoginOptions

func (s *Service) LoginOptions(ctx context.Context) ([]string, error)

LoginOptions returns the login-options array: one "common-oidc/<json>" item (wins in the client) plus one "oidc/<slug>" per enabled provider.

func (*Service) Poll

func (s *Service) Poll(ctx context.Context, code, id, uuid string, r *http.Request) any

Poll implements GET /api/oidc/auth-query. It always returns a JSON body: {"error":"No authed oidc is found"} while pending, {"error": msg} on failure, or an AuthBody once (the code is consumed on the first answer that is not "pending"). The poll must present the same device id and uuid that started the flow; otherwise the code is not touched.

type UserInfo

type UserInfo struct {
	EmailVerification      bool `json:"email_verification"`
	EmailAlarmNotification bool `json:"email_alarm_notification"`
}

UserInfo is the required "info" object of the client UserPayload.

type UserPayload

type UserPayload struct {
	Name        string   `json:"name"`
	DisplayName string   `json:"display_name"`
	Avatar      string   `json:"avatar"`
	Email       string   `json:"email"`
	Note        string   `json:"note"`
	IsAdmin     bool     `json:"is_admin"`
	Status      int      `json:"status"`
	Info        UserInfo `json:"info"`
}

UserPayload is the user object of the client auth body. The Rust client requires "name" and "info" and accepts status only in {0, 1, -1}.

func NewUserPayload

func NewUserPayload(u *store.User) UserPayload

NewUserPayload builds the client user object.

type WebLoginFunc

type WebLoginFunc func(w http.ResponseWriter, r *http.Request, user *store.User, redirectPath string)

WebLoginFunc completes a panel OIDC login: it must create the session (rotating any existing one), set cookies and write the redirect response. redirectPath is already validated as a safe relative path.

Directories

Path Synopsis
Package oidctest provides fake identity providers (generic OIDC with discovery + signed ID tokens, and GitHub) for tests.
Package oidctest provides fake identity providers (generic OIDC with discovery + signed ID tokens, and GitHub) for tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL