api

package
v1.0.9 Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

Documentation

Index

Constants

View Source
const (
	FlowPlain        = "plain"
	FlowPKCE         = "pkce"
	FlowPKCEImplicit = "pkce_implicit"
	FlowVerify       = "verify"
)

Flow constants for authorization_code client sub-flows.

Variables

View Source
var (
	// ContextKeyAccessToken is the plaintext bearer token, populated by
	// BearerTokenHandler after successful token validation. Promoted to a
	// typed key for type-safe consumer access; the legacy string key
	// "accessToken" is still set for one major release for backward compat.
	ContextKeyAccessToken = contextKey{/* contains filtered or unexported fields */}

	// ContextKeyClientID is the integer ID of the authenticated oauth_clients
	// row, populated by BearerTokenHandler after successful token validation.
	ContextKeyClientID = contextKey{/* contains filtered or unexported fields */}

	// ContextKeyClientName is the human-readable name of the authenticated
	// oauth_clients row (oauth_clients.name), populated by BearerTokenHandler
	// after successful token validation. Consumers use this for per-client
	// audit logging, multi-tenant routing, or vendor attribution on outbound
	// calls.
	ContextKeyClientName = contextKey{/* contains filtered or unexported fields */}
)
View Source
var (
	ErrInvalidRequest                 = errors.New("invalid_request")
	ErrUnauthorizedClient             = errors.New("unauthorized_client")
	ErrAccessDenied                   = errors.New("access_denied")
	ErrUnsupportedResponseType        = errors.New("unsupported_response_type")
	ErrInvalidScope                   = errors.New("invalid_scope")
	ErrServerError                    = errors.New("server_error")
	ErrTemporarilyUnavailable         = errors.New("temporarily_unavailable")
	ErrInvalidClient                  = errors.New("invalid_client")
	ErrInvalidGrant                   = errors.New("invalid_grant")
	ErrUnsupportedGrantType           = errors.New("unsupported_grant_type")
	ErrCodeChallengeRequired          = errors.New("invalid_request")
	ErrUnsupportedCodeChallengeMethod = errors.New("invalid_request")
	ErrInvalidCodeChallengeLen        = errors.New("invalid_request")
	ErrInvalidCodeChallenge           = errors.New("invalid code challenge")
	ErrExpiredRefreshToken            = errors.New("expired refresh token")
	ErrInvalidRefreshToken            = errors.New("invalid refresh token")
	ErrInvalidRedirectURI             = errors.New("invalid redirect uri")
)

OAuth2 error types per RFC 6749 Section 5.2. Use these with NewErrorResponse to create properly formatted error responses.

Example:

if client == nil {
	return nil, NewErrorResponse(ErrInvalidClient)
}

DB is the database session used for OAuth2 operations.

View Source
var Descriptions = map[error]string{
	ErrInvalidRequest:                 "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
	ErrUnauthorizedClient:             "The client is not authorized to request an authorization code using this method.",
	ErrAccessDenied:                   "The resource owner or authorization server denied the request.",
	ErrUnsupportedResponseType:        "The authorization server does not support obtaining an authorization code using this method.",
	ErrInvalidScope:                   "The requested scope is invalid, unknown, or malformed.",
	ErrServerError:                    "The authorization server encountered an unexpected condition that prevented it from fulfilling the request.",
	ErrTemporarilyUnavailable:         "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.",
	ErrInvalidClient:                  "Client authentication failed.",
	ErrInvalidGrant:                   "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.",
	ErrUnsupportedGrantType:           "The authorization grant type is not supported by the authorization server.",
	ErrCodeChallengeRequired:          "PKCE is required. code_challenge is missing.",
	ErrUnsupportedCodeChallengeMethod: "Selected code_challenge_method not supported.",
	ErrInvalidCodeChallengeLen:        "Code challenge length must be between 43 and 128 characters.",
	ErrInvalidRedirectURI:             "The redirect URI provided is not a valid URI.",
}

descriptions are base on https://github.com/go-oauth2/oauth2/blob/master/errors/response.go and follow RFC 6749

status codes are based on https://github.com/go-oauth2/oauth2/blob/master/errors/response.go and follow RFC 6749

Functions

func BearerTokenHandler

func BearerTokenHandler(unguardedRoute []string, GuardedRouteGroups []string, ErrorLogger *log.Logger, o *Service) func(next http.Handler) http.Handler

authenticate the bearer token attached to a HTTP request is a valid token by getting it by the plain text value from the db and checking that is not expired. Valid tokens are added to the context of the request as an access token for use by other middleware deeper in the stack. The middleware is designed for use in the global stack, so it is going to get loaded on every request, as a result the the guarded route groups are first checked and quickly passed to the next middleware if the path is not in a guarded group.

func ChallengeCodeValidate

func ChallengeCodeValidate(challengeCode, method string) (bool, error)

ChallengeCodeValidate validates a PKCE code challenge and method per RFC 7636. The challenge method must be "S256" and the code must be 43-128 characters.

Example:

ok, err := ChallengeCodeValidate("abc123...xyz", "S256")
if err != nil {
	return nil, NewErrorResponse(err)
}

Validation rules:

  • method must be "S256" (plain is not supported)
  • challengeCode must not be empty
  • challengeCode length must be between 43 and 128 characters

func GenerateTokenExpiry

func GenerateTokenExpiry(hours int) time.Time

GenerateTokenExpiry creates a token expiry time for use with any type of tokens issued by the authorization server. Currently returns 24 hours from now in UTC.

Example:

expiry := facades.GenerateTokenExpiry(24)
token.Expires = expiry

func IsExpired

func IsExpired(token interface{}) bool

IsExpired checks if the provided token has expired by comparing its 'Expires' field with the current UTC time. Returns true if still valid, false if expired. Works with any struct that has an Expires field of type time.Time.

Example:

token := &OauthToken{Expires: time.Now().Add(-1 * time.Hour)} // expired
if !IsExpired(token) {
	fmt.Println("Token has expired")
}

func ScopeHandler

func ScopeHandler(UnguardedRoutes []string, GuardedRouteGroups []string, ErrorLogger *log.Logger, o *Service) func(next http.Handler) http.Handler

look up the scopes assigned to the current route and confirm the access token passed in the http request has the same scopes assigned to the token.

Types

type AuthorizationResponse

type AuthorizationResponse struct {
	GrantType   string      `json:"-"`
	TokenType   string      `json:"token_type"`
	Code        string      `json:"code"`
	State       string      `json:"state"`
	RedirectUri RedirectUri `json:"redirectURL"`
	CSRFToken   string      `json:"-"`
}

AuthorizationResponse represents the response from an authorization request. Used in authorization code and PKCE flows.

Example:

response := &AuthorizationResponse{
	Code:      "auth_code_123",
	State:     "xyz",
	TokenType: "code",
}

type AuthorizationToken

type AuthorizationToken struct {
	ID                  int       `db:"id,omitempty" json:"id"`
	UserID              *int      `db:"user_id,omitempty" json:"user_id"`
	ClientID            int       `db:"client_id" json:"client_id"`
	PlainText           string    `db:"-" json:"token"`
	Hash                []byte    `db:"token_hash" json:"-"`
	CreatedAt           time.Time `db:"created_at" json:"created_at"`
	UpdatedAt           time.Time `db:"updated_at" json:"updated_at"`
	Expires             time.Time `db:"expiry" json:"expiry"`
	ChallengeCode       string    `db:"challenge_code,omitempty" json:"ChallengeCode"`
	ChallengeCodeMethod string    `db:"challenge_code_method,omitempty" json:"ChallengeCodeMethod"`
	State               string    `db:"-" json:"state"`
}

type Client

type Client struct {
	ID          int       `db:"id,omitempty" json:"id"`
	UserID      *int      `db:"user_id,omitempty" json:"user_id"`
	Secret      string    `db:"secret" json:"secret"`
	PlainText   string    `db:"-" json:"-"`
	Name        string    `db:"name"`
	Revoked     int       `db:"revoked"`
	Type        string    `db:"type"`
	Flow        string    `db:"flow" json:"flow"`
	RedirectUrl string    `db:"redirect_url,omitempty" json:"redirectURL"`
	CreatedAt   time.Time `db:"created_at"`
	UpdatedAt   time.Time `db:"updated_at"`
}

Client represents an OAuth2 client application registered with the server. Type contains the RFC 6749 grant type: "authorization_code", "client_credentials", or "password". Flow discriminates authorization_code sub-flows: "plain", "pkce", or "pkce_implicit".

Example:

client := &Client{
	Name:        "My App",
	Secret:      "client_secret_here",
	Type:        "client_credentials",
	RedirectUrl: "https://myapp.com/callback",
}

type Configuration

type Configuration struct {
	PkceImplicitAuthorizationScopes map[string]string `yaml:"PkceImplicitAuthorizationScopes"` // scopes that the authorization server may assign to a pkce implicit authorization code request
	Scopes                          Scopes            `yaml:"Scopes"`                          // scopes that the authorization server may assign to a oauth token request
	UnguardedRoutes                 []string          `yaml:"UnguardedRoutes"`                 // paths that do not require authentication (relative paths may not omit leading slash)
	GuardedRouteGroups              []string          `yaml:"GuardedRouteGroups"`              // paths that require authentication (relative paths may omit leading slash)
	AuthorizationTokenTTL           time.Duration     `yaml:"AuthorizationTokenTTL"`           // duration the authorization token is valid (default: 60 minutes)
	OauthTokenTTL                   time.Duration     `yaml:"OauthTokenTTL"`                   // duration a token is valid (default: 24 hours)
	PkceImplicitTTL                 time.Duration     `yaml:"PkceImplicitTTL"`                 // duration a PKCE implicit token is valid (default: 300 seconds)
	RefreshTokenTokenTTL            time.Duration     `yaml:"RefreshTokenTokenTTL"`            // duration a refresh token is valid (default: 24 hours)
	VerifyTemplatePath              string            `yaml:"VerifyTemplatePath"`              // the path to the verify view template relative to the resources dir
}

Configuration holds the OAuth2 server configuration loaded from oauth.yml.

Example (oauth.yml):

Scopes:
  read: "Read access"
  write: "Write access"
UnguardedRoutes:
  - /oauth/token
  - /health
OauthTokenTTL: 24
RefreshTokenTokenTTL: 168

type ErrorResponse

type ErrorResponse struct {
	Description string `json:"error_description"`
	Error       string `json:"error"`
	ErrorCode   int    `json:"-"`
	URI         string `json:"error_uri,omitempty"`
}

ErrorResponse represents an OAuth2 error response per RFC 6749.

Example:

err := NewErrorResponse(ErrInvalidClient)
// Returns:
// {
//   "error": "invalid_client",
//   "error_description": "Client authentication failed."
// }

func NewErrorResponse

func NewErrorResponse(err error) *ErrorResponse

NewErrorResponse creates an ErrorResponse from an error type. Maps the error to its RFC 6749 description and HTTP status code.

Example:

err := NewErrorResponse(ErrInvalidClient)
// err.Error = "invalid_client"
// err.Description = "Client authentication failed."
// err.ErrorCode = 401

// Use with JSON response:
w.WriteHeader(err.ErrorCode)
json.NewEncoder(w).Encode(err)

Note: ErrorCode is used for HTTP status only and is not included in the JSON response. The JSON output uses error_description (Description) and error_uri (URI) per RFC 6749.

type OauthResponse

type OauthResponse struct {
	GrantType    string `json:"-"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int64  `json:"expires_in"`
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token,omitempty"`
	Scope        string `json:"scope,omitempty"`
}

OauthResponse represents the response from a token exchange request. Contains the access token and optionally a refresh token.

Example:

// Response JSON:
// {
//   "token_type": "Bearer",
//   "access_token": "eyJhbGciOiJIUzI1NiIs...",
//   "refresh_token": "refresh_abc123",
//   "expires_in": 3600
// }

type OauthToken

type OauthToken struct {
	ID           int       `db:"id,omitempty" json:"id"`
	UserID       *int      `db:"user_id,omitempty" json:"user_id"`
	ClientID     int       `db:"client_id" json:"client_id"`
	PlainText    string    `db:"-" json:"token"`
	Hash         []byte    `db:"token_hash" json:"-"`
	CreatedAt    time.Time `db:"created_at" json:"created_at"`
	UpdatedAt    time.Time `db:"updated_at" json:"updated_at"`
	Expires      time.Time `db:"expiry" json:"expiry"`
	RefreshToken string    `db:"-" json:"refresh_token"`
	Scopes       string    `db:"scopes,omitempty" json:"scopes"`
}

type RedirectUri

type RedirectUri struct {
	URI   string
	Path  string
	Query string
}

type RefreshToken

type RefreshToken struct {
	ID            int       `db:"id,omitempty" json:"id"`
	AccessTokenID int       `db:"access_token_id" json:"-"`
	Expires       time.Time `db:"expiry" json:"expiry"`
	Hash          []byte    `db:"token_hash" json:"-"`
	PlainText     string    `db:"-" json:"token"`
	CreatedAt     time.Time `db:"created_at" json:"created_at"`
	UpdatedAt     time.Time `db:"updated_at" json:"updated_at"`
}

type Scopes

type Scopes = map[string]string

Scopes represents a map of scope names to their descriptions.

Example:

scopes := Scopes{
	"read":  "Read access to resources",
	"write": "Write access to resources",
}

type Service

type Service struct {
	//Auth       *auth.Auth
	DB         *database.Database
	GrantTypes map[string]string
	Config     Configuration
	ErrorLog   *log.Logger
	Renderer   *render.Render
	Session    *scs.SessionManager
	Mux        *mux.Mux
}

Service provides the core OAuth2 functionality including token generation, validation, and client management.

Example:

facades := facades.New(adeleApp)
token, err := facades.GenerateOauthToken()

func New

func New(a *adele.Adele) (Service, error)

New creates a new Service instance with the given Adele application. It initializes the database session and loads configuration from config/oauth.yml.

Example:

svc, err := api.New(adeleApp)
if err != nil {
    return err
}

func NewWithConfig

func NewWithConfig(a *adele.Adele, config Configuration) (Service, error)

NewWithConfig creates a new Service instance with a custom configuration. Used primarily for testing.

Example:

cfg := api.Configuration{Scopes: map[string]string{"read": "Read access"}}
svc, err := api.NewWithConfig(adeleApp, cfg)
if err != nil {
    return err
}

func (*Service) AccessTokenGrantExchange

func (o *Service) AccessTokenGrantExchange(w http.ResponseWriter, r *http.Request) (*OauthResponse, *ErrorResponse)

AccessTokenGrantExchange exchanges credentials for an access token. Supports client_credentials, password, and authorization_code (PKCE code exchange) grant types.

Example (client_credentials):

// POST /oauth/token
// Content-Type: application/x-www-form-urlencoded
// client_id=1&client_secret=secret&grant_type=client_credentials&scopes=read

Example (PKCE code exchange):

// POST /oauth/token
// client_id=1&code=auth_code&code_verifier=verifier&
// code_challenge_method=S256&grant_type=authorization_code&scopes=read

response, err := facades.AccessTokenGrantExchange(w, r)
// response.AccessToken contains the bearer token
// response.RefreshToken contains the refresh token (if applicable)

func (*Service) AnyScope

func (o *Service) AnyScope(r *http.Request, anyScopes Scopes) bool

Check if the token has at least one scope.

func (*Service) AuthenticateToken

func (o *Service) AuthenticateToken(r *http.Request) (bool, *OauthToken, error)

token authentication attached to a http request in the form of bearer token.

func (*Service) AuthenticationCheckForScopes

func (o *Service) AuthenticationCheckForScopes() func(next http.Handler) http.Handler

func (*Service) AuthenticationTokenMiddleware

func (o *Service) AuthenticationTokenMiddleware() func(next http.Handler) http.Handler

func (*Service) AuthorizationClientCodeExchange

func (o *Service) AuthorizationClientCodeExchange(w http.ResponseWriter, r *http.Request, client *Client) (*AuthorizationResponse, *ErrorResponse)

func (*Service) AuthorizationClientCodeExchangeImplicit

func (o *Service) AuthorizationClientCodeExchangeImplicit(w http.ResponseWriter, r *http.Request, client *Client) (*AuthorizationResponse, *ErrorResponse)

func (*Service) AuthorizationClientExchange

func (o *Service) AuthorizationClientExchange(w http.ResponseWriter, r *http.Request, client *Client) (*AuthorizationResponse, *ErrorResponse)

func (*Service) AuthorizationGrantExchange

func (o *Service) AuthorizationGrantExchange(w http.ResponseWriter, r *http.Request) (*AuthorizationResponse, *ErrorResponse)

AuthorizationGrantExchange validates an authorization request and returns the redirect URI. This is the first step in the authorization code flow (GET request).

Required form fields: client_id, grant_type, response_type, redirect_uri, state, code_challenge, code_challenge_method, scopes

REQUIREMENT: This handler stores intermediate OAuth state (CSRF token, and on the POST counterpart user_id and the in-flight authorization code / PKCE challenge) in the user session via o.Session.Put. The request MUST be served through the framework's session.LoadAndSave middleware (auto-mounted around every route registered on a.Routes) so that scs has session data wired into r.Context(). Invoking this handler directly with a request whose context has not been hydrated by LoadAndSave will panic ("scs: no session data in context"). Tests that exercise this handler must call o.Session.Load on the request context (see oauth_test.go) or wrap the handler in o.Session.LoadAndSave before serving.

Example:

// GET /oauth/authorize?client_id=1&grant_type=authorization_code&response_type=code&
//     redirect_uri=https://app.com/callback&state=xyz&code_challenge=abc&
//     code_challenge_method=S256&scopes=read%20write

response, err := facades.AuthorizationGrantExchange(w, r)
if err != nil {
	// Handle error
}
// Redirect to response.RedirectUri.URI

func (*Service) AuthorizationGrantExchangePost

func (o *Service) AuthorizationGrantExchangePost(w http.ResponseWriter, r *http.Request) (*AuthorizationResponse, *ErrorResponse)

AuthorizationGrantExchangePost processes the authorization form submission. Handles plain, pkce, and pkce_implicit authorization code flows.

Example:

// POST /oauth/authorize
// Content-Type: application/x-www-form-urlencoded
// client_id=1&grant_type=authorization_code&response_type=code&...

response, err := facades.AuthorizationGrantExchangePost(w, r)
if err != nil {
	// Handle error
}
// For PKCE: redirect to response.RedirectUri.URI with code
// For implicit: return response with token

func (*Service) CheckUserPasswordMatches

func (o *Service) CheckUserPasswordMatches(plainText string, user User) bool

func (*Service) ConsumeAuthorizationToken

func (o *Service) ConsumeAuthorizationToken(plainText string) (*AuthorizationToken, error)

ConsumeAuthorizationToken atomically retrieves and deletes an authorization token. Returns the token if found and deleted, nil if already consumed or not found.

func (*Service) DeleteAuthorizationToken

func (o *Service) DeleteAuthorizationToken(id int) error

delete a authorization token by id from the database and return an error if necessary

func (*Service) DeleteOauthToken

func (o *Service) DeleteOauthToken(id int) error

func (*Service) DeleteRefreshTokenByToken

func (o *Service) DeleteRefreshTokenByToken(plainText string) error

Delete a refresh token from db by deriving its hash from the plain text token.

func (*Service) GenerateAuthorizationToken

func (o *Service) GenerateAuthorizationToken() (*AuthorizationToken, error)

Create a new authorization token

func (*Service) GenerateOauthToken

func (o *Service) GenerateOauthToken() (*OauthToken, error)

Create a oauth token that is always the same length each time one is generated.

func (*Service) GenerateRefreshToken

func (o *Service) GenerateRefreshToken(userID int, AccessTokenID int, clientID int) (*RefreshToken, error)

Generate a string representing the authorization granted to the client by the resource owner. The string is usually opaque to the client. The token denotes an identifier used to retrieve the authorization information. https://datatracker.ietf.org/doc/html/rfc6749#section-1.5

func (*Service) GetAuthTokenFromHeader

func (o *Service) GetAuthTokenFromHeader(r *http.Request) (*OauthToken, error)

extract a token from the http request header

func (*Service) GetAuthenticatedUser

func (o *Service) GetAuthenticatedUser(r *http.Request) *User

Get the current authenticated user

func (*Service) GetAuthorizationTokenByToken

func (o *Service) GetAuthorizationTokenByToken(plainText string) (*AuthorizationToken, error)

Get a authorization token by hashing the plain text value and querying token_hash.

func (*Service) GetByToken

func (o *Service) GetByToken(plainText string) (*OauthToken, error)

get the token from the db by hashing the plain text value and querying token_hash

func (*Service) GetClient

func (o *Service) GetClient(id int) (*Client, error)

Get the client by the ID and if no client is found the return value will be nil.

func (*Service) GetOauthToken

func (o *Service) GetOauthToken(id int) (*OauthToken, error)

func (*Service) GetRefreshByToken

func (o *Service) GetRefreshByToken(plainText string) (*RefreshToken, error)

Find a refresh token in the db by hashing the plain text value and querying token_hash.

func (*Service) GetUserByEmail

func (o *Service) GetUserByEmail(email string) (*User, error)

func (*Service) HasScope

func (o *Service) HasScope(r *http.Request, requiredScopes Scopes) bool

Check if the token in the request has all scope(s)

func (*Service) InsertAuthorizationToken

func (o *Service) InsertAuthorizationToken(token *AuthorizationToken) (*AuthorizationToken, error)

Add a authorization token to the database and return the token id in the response

func (*Service) InsertClient

func (o *Service) InsertClient(client Client) (*Client, error)

InsertClient hashes the client secret with bcrypt and persists the client to the database. The plaintext secret is available in the returned Client's PlainText field — it is shown to the caller once and never stored.

func (*Service) InsertOauthToken

func (o *Service) InsertOauthToken(token *OauthToken) (*OauthToken, error)

Add a token to the db and return the token id in the response

func (*Service) InsertRefreshToken

func (o *Service) InsertRefreshToken(token *RefreshToken) error

Add a authorization token to the db and return the token id in the response

func (*Service) RefreshTokenExchange

func (o *Service) RefreshTokenExchange(w http.ResponseWriter, r *http.Request) (*OauthResponse, *ErrorResponse)

RefreshTokenExchange exchanges a valid refresh token for a new access token. Per RFC 6749, the new token's scopes must be a subset of the original token's scopes.

Required fields: client_id, client_secret, grant_type (must be "refresh_token"), refresh_token, scopes

Example:

// POST /oauth/token/refresh
// Content-Type: application/x-www-form-urlencoded
// client_id=1&client_secret=secret&grant_type=refresh_token&
// refresh_token=abc123&scopes=read

response, err := facades.RefreshTokenExchange(w, r)
// response.AccessToken = new access token
// response.RefreshToken = new refresh token

func (*Service) ResourceOwnerTokenExchange

func (o *Service) ResourceOwnerTokenExchange(r *http.Request, w http.ResponseWriter, client Client) (*OauthToken, *RefreshToken, *ErrorResponse)

func (*Service) TokenIsExpired

func (o *Service) TokenIsExpired(token interface{}) bool

TokenIsExpired checks if the provided token has expired by comparing its 'Expires' field with the current UTC time. Returns true if the token is still valid (not expired), false if expired or if the token doesn't have an Expires field.

Example:

token := &OauthToken{Expires: time.Now().Add(1 * time.Hour)}
if facades.TokenIsExpired(token) {
	fmt.Println("Token is still valid")
}

func (*Service) UserIsLoggedIn

func (o *Service) UserIsLoggedIn(r *http.Request) bool

search for the suer in the session and return a bool if exists

func (*Service) ValidateClientRedirect

func (o *Service) ValidateClientRedirect(uri string, client *Client) bool

Validate that a given uri can be used for redirection.

func (*Service) VerifyAuthorizationCode

func (o *Service) VerifyAuthorizationCode(token AuthorizationToken, codeVerifier string) bool

Verify by calculating the code challenge from the received "code_verifier" and comparing it with the previously associated "code_challenge" after first transforming it according to the "code_challenge_method" method specified by the client. The formula for this is BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge, but we will need to replace the "=" for padding as it would be parsed by the URL in the browser and converted to a URL safe encoded character.

type User

type User struct {
	ID        int        `db:"id,omitempty"`
	FirstName string     `db:"first_name"`
	LastName  string     `db:"last_name"`
	Email     string     `db:"email"`
	Active    int        `db:"user_active"`
	Password  string     `db:"password"`
	CreatedAt time.Time  `db:"created_at"`
	UpdatedAt time.Time  `db:"updated_at"`
	Token     OauthToken `db:"-"`
}

Jump to

Keyboard shortcuts

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