auth

package module
v0.0.0-...-2535ed7 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2020 License: MIT Imports: 35 Imported by: 7

README

Auth

Auth is a modular authentication system for web development in Golang, it provides different authentication backends to accelerate your development.

Currently Auth has database password, github, google, facebook, twitter authentication support, and it is fairly easy to add other support based on Auth's Provider interface

Quick Start

Auth aims to provide an easy to use authentication system that don't require much developer's effort.

To use it, basic flow is:

  • Initialize Auth with configuration
  • Register some providers
  • Register it into router

Here is an example:

import (
  "github.com/ecletus/auth"
  "github.com/ecletus/auth/auth_identity"
  "github.com/ecletus/auth/providers/github"
  "github.com/ecletus/auth/providers/google"
  "github.com/ecletus/auth/providers/password"
  "github.com/ecletus/auth/providers/facebook"
  "github.com/ecletus/auth/providers/twitter"
  "github.com/ecletus/session/manager"
)

var (
  // Initialize gorm DB
  gormDB, _ = aorm.Open("sqlite3", "sample.db")

  // Initialize Auth with configuration
  Auth = auth.New(&auth.Config{
    DB: gormDB,
  })
)

func init() {
  // Migrate AuthIdentity model, AuthIdentity will be used to save auth info, like username/password, oauth token, you could change that.
  gormDB.AutoMigrate(&auth_identity.AuthIdentity{})

  // Register Auth providers
  // Allow use username/password
  Auth.RegisterProvider(password.New(&password.Config{}))

  // Allow use Github
  Auth.RegisterProvider(github.New(&github.Config{
    ClientID:     "github client id",
    ClientSecret: "github client secret",
  }))

  // Allow use Google
  Auth.RegisterProvider(google.New(&google.Config{
    ClientID:     "google client id",
    ClientSecret: "google client secret",
  }))

  // Allow use Facebook
  Auth.RegisterProvider(facebook.New(&facebook.Config{
    ClientID:     "facebook client id",
    ClientSecret: "facebook client secret",
  }))

  // Allow use Twitter
  Auth.RegisterProvider(twitter.New(&twitter.Config{
    ClientID:     "twitter client id",
    ClientSecret: "twitter client secret",
  }))
}

func main() {
  mux := http.NewServeMux()

  // Mount Auth to Router
  mux.Handle("/auth/", Auth.NewServeMux())
  http.ListenAndServe(":9000", manager.SessionManager.Middleware(mux))
}

That's it, then you could goto http://127.0.0.1:9000/auth/login to try Auth features, like login, logout, register, forgot/change password...

And it could be even easier with Auth Themes, you could integrate Auth into your application with few line configurations.

Usage

Auth has many configurations that could be used to customize it for different usage, lets start from Auth's Config.

Models

Auth has two models, model AuthIdentityModel is used to save login information, model UserModel is used to save user information.

The reason we save auth and user info into two different models, as we want to be able to link a user to mutliple auth info records, so a user could have multiple ways to login.

If this is not required for you, you could just set those two models to same one or skip set UserModel.

  • AuthIdentityModel

Different provider usually use different information to login, like provider password use username/password, github use github user ID, so for each provider, it will save those information into its own record.

You are not necessary to set AuthIdentityModel, Auth has a default definition of AuthIdentityModel, in case of you want to change it, make sure you have auth_identity.Basic embedded, as Auth assume you have same data structure in your database, so it could query/create records with SQL.

  • UserModel

By default, there is no UserModel defined, even though, you still be able to use Auth features, Auth will return used auth info record as logged user.

But usually your application will have a User model, after you set its value, when you register a new account from any provider, Auth will create/get a user with UserStorer, and link its ID to the auth identity record.

Customize views

Auth using Render to render pages, you could refer it for how to register func maps, extend views paths, also be sure to refer BindataFS if you want to compile your application into a binary.

If you want to preprend view paths, you could add them to ViewPaths, which would be helpful if you want to overwrite the default (ugly) login/register pages or develop auth themes like https://github.com/ecletus/auth_themes

Sending Emails

Auth using Mailer to send emails, by default, Auth will print emails to console, please configure it to send real one.

User Storer

Auth created a default UserStorer to get/save user based on your AuthIdentityModel, UserModel's definition, in case of you want to change it, you could implement your own User Storer

Session Storer

Auth also has a default way to handle sessions, flash messages, which could be overwrited by implementing Session Storer Interface.

By default, Auth is using session's default manager to save data into cookies, but in order to save cookies correctly, you have to register session's Middleware into your router, e.g:

func main() {
	mux := http.NewServeMux()

	// Register Router
	mux.Handle("/auth/", Auth.NewServeMux())
	http.ListenAndServe(":9000", manager.SessionManager.Middleware(mux))
}
Redirector

After some Auth actions, like logged, registered or confirmed, Auth will redirect user to some URL, you could configure which page to redirect with Redirector, by default, will redirct to home page.

If you want to redirect to last visited page, redirect_back is for you, you could configure it and use it as the Redirector, like:

var RedirectBack = redirect_back.New(&redirect_back.Config{
	SessionManager:  manager.SessionManager,
	IgnoredPrefixes: []string{"/auth"},
}

var Auth = auth.New(&auth.Config{
	...
	Redirector: auth.Redirector{RedirectBack},
})

BTW, to make it works correctly, redirect_back need to save last visisted URL into session with session manager for each request, that's means, you need to mount redirect_back, and SessionManager's middleware into router.

http.ListenAndServe(":9000", manager.SessionManager.Middleware(RedirectBack.Middleware(mux)))

Advanced Usage

Auth Themes

In order to save more developer's effort, we have created some auth themes.

It usually has well designed pages, if you don't much custom requirements, you could just have few lines to make Auth system ready to use for your application, for example:

import "github.com/ecletus/auth_themes/clean"

var Auth = clean.New(&auth.Config{
	DB:         db.DB,
	Render:     config.View,
	Mailer:     config.Mailer,
	UserModel:  models.User{},
})

Check Auth Theme's document for How To use/create Auth themes

Authorization

Authentication is the process of verifying who you are, Authorization is the process of verifying that you have access to something.

Auth package not only provides Authentication, but also Authorization, please checkout authority for more details

Documentation

Index

Constants

View Source
const (
	ErrInvalidPassword                = auth_errors.ErrInvalidPassword
	ErrInvalidAccount                 = auth_errors.ErrInvalidAccount
	ErrUnauthorized                   = auth_errors.ErrUnauthorized
	ErrUidBlank                       = auth_errors.ErrUidBlank
	ErrMaximumNumberOfAccessesReached = auth_errors.ErrMaximumNumberOfAccessesReached
)
View Source
const CurrentUser utils.ContextKey = "qor:auth.current_user"

CurrentUser context key to get current user from Request

Variables

View Source
var AUTH_URL_KEY = PREFIX + ".auth.url"
View Source
var DefaultAssetHandler = func(context *Context) {
	pth := strings.TrimPrefix(context.Request.URL.Path, context.Auth.URLPrefix)

	if context.Request.Header.Get("If-Modified-Since") == cacheSince {
		context.Writer.WriteHeader(http.StatusNotModified)
		return
	}
	context.Writer.Header().Set("Last-Modified", cacheSince)

	if asset, err := context.Auth.Config.Render.Asset(path.Join("/auth", pth)); err == nil {
		etag := fmt.Sprintf("%x", md5.Sum(assetfs.MustData(asset)))
		if context.Request.Header.Get("If-None-Match") == etag {
			context.Writer.WriteHeader(http.StatusNotModified)
			return
		}

		if ctype := mime.TypeByExtension(filepath.Ext(pth)); ctype != "" {
			context.Writer.Header().Set("Content-Type", ctype)
		}

		context.Writer.Header().Set("Cache-control", "private, must-revalidate, max-age=300")
		context.Writer.Header().Set("ETag", etag)
		context.Writer.Write(assetfs.MustData(asset))
	} else {
		http.NotFound(context.Writer, context.Request)
	}
}

DefaultAssetHandler render auth asset file

View Source
var DefaultLoginHandler = func(context *LoginContext, authorize func(*LoginContext) (*claims.Claims, error)) (claims *claims.Claims, err error) {
	if claims, err = authorize(context); err == nil {
		if err = context.Auth.Signed(context.Context.Context, claims); err != nil {
			if err == ErrMaximumNumberOfAccessesReached {
				http.Error(context.Writer, err.Error(), http.StatusForbidden)
				return
			}
			http.Error(context.Writer, err.Error(), http.StatusInternalServerError)
			return
		}
		return
	}
	return
}

DefaultLoginHandler default login behaviour

View Source
var DefaultLogoutHandler = func(context *Context) {

	context.SessionStorer.Delete(context.SessionManager())
	context.Auth.Redirector.Redirect(context.Writer, context.Request, "logout")
}

DefaultLogoutHandler default logout behaviour

View Source
var DefaultProfileHandler = func(context *Context) {

	responder.With("html", func() {
		context.Auth.Config.Render.Execute("auth/profile", context, context.Context)
	}).With([]string{"json"}, func() {

	}).Respond(context.Request)
}

DefaultProfileHandler default profile behaviour

View Source
var DefaultRegisterHandler = func(context *Context, register func(*Context) (*claims.Claims, error)) {
	claims, _ := register(context)
	respond(&LoginContext{Context: context}, claims, "auth/register")
}

DefaultRegisterHandler default register behaviour

View Source
var ErrNoSession = errors.New("no session")
View Source
var I18N_GROUP = i18nmod.PkgToGroup(PREFIX)
View Source
var PREFIX = path_helpers.GetCalledDir()

Functions

func Authenticates

func Authenticates(authp interface{}, w http.ResponseWriter, r *http.Request, f func(bool))

func GetUser

func GetUser(ctx context.Context) common.User

func GetUserI

func GetUserI(ctx context.Context) interface{}

func I18n

func I18n(key ...string) string

Types

type Auth

type Auth struct {
	*Config
	// Embed SessionStorer to match Authority's AuthInterface
	SessionStorerInterface

	Funcs funcs.FuncValues
	// contains filtered or unexported fields
}

Auth auth struct

func New

func New(config *Config) *Auth

New initialize Auth

func (*Auth) AfterLogged

func (auth *Auth) AfterLogged(f ...func(ctx *LoginContext, claims *claims.Claims) error) *Auth

func (*Auth) AuthPath

func (auth *Auth) AuthPath(pth ...string) string

AuthPath generate URL for auth

func (*Auth) BeforeLogin

func (auth *Auth) BeforeLogin(f ...func(ctx *LoginContext) error) *Auth

func (*Auth) FindUID

func (auth *Auth) FindUID(ctx *Context, identifier string) (uid string, err error)

Login sign user in

func (*Auth) GetCurrentUser

func (auth *Auth) GetCurrentUser(req *http.Request) (currentUser common.User, err error)

GetCurrentUser get current user from request

func (*Auth) GetCurrentUserClaims

func (auth *Auth) GetCurrentUserClaims(req *http.Request) (currentUser common.User, Claims *claims.Claims, err error)

GetCurrentUserClaims get current user and Claims from request

func (*Auth) GetProvider

func (auth *Auth) GetProvider(name string) Provider

GetProvider get provider with name

func (*Auth) GetProviders

func (auth *Auth) GetProviders() (providers []Provider)

GetProviders return registered providers

func (*Auth) I18n

func (auth *Auth) I18n(key ...string) string

func (*Auth) Intercept

func (auth *Auth) Intercept(w http.ResponseWriter, r *http.Request, handler http.Handler)

func (*Auth) InterceptFunc

func (auth *Auth) InterceptFunc(w http.ResponseWriter, r *http.Request, f func())

func (*Auth) Login

func (auth *Auth) Login(ctx *LoginContext) (Claims *claims.Claims, err error)

Login sign user in

func (*Auth) LoginInfo

func (auth *Auth) LoginInfo(f ...func(ctx *LoginContext, info *auth_identity.Basic) error) *Auth

func (*Auth) Logout

func (auth *Auth) Logout(w http.ResponseWriter, req *http.Request)

Logout sign current user out

func (*Auth) Middleware

func (auth *Auth) Middleware() *xroute.Middleware

func (*Auth) NewContextFromRequest

func (auth *Auth) NewContextFromRequest(r *http.Request, values ...interface{}) (*http.Request, *Context)

func (*Auth) NewContextFromRequestPair

func (auth *Auth) NewContextFromRequestPair(w http.ResponseWriter, r *http.Request, values ...interface{}) (*http.Request, *Context)

func (*Auth) NewContextFromSite

func (auth *Auth) NewContextFromSite(site *core.Site) *Context

func (*Auth) NewServeMux

func (auth *Auth) NewServeMux() http.Handler

NewServeMux generate http.Handler for auth

func (*Auth) RegisterProvider

func (auth *Auth) RegisterProvider(provider Provider)

RegisterProvider register auth provider

func (*Auth) Registrable

func (auth *Auth) Registrable(context *core.Context) bool

func (*Auth) Signed

func (auth *Auth) Signed(ctx *core.Context, Claims *claims.Claims) (err error)

Login sign user in

func (*Auth) SignedCallback

func (auth *Auth) SignedCallback(f ...func(ctx *core.Context, Claims *claims.Claims) error) *Auth

func (*Auth) URL

func (auth *Auth) URL(r *http.Request) *AuthURL

type AuthBeforer

type AuthBeforer interface {
	BeforeAuth(ctx *LoginContext) error
}

type AuthFailurer

type AuthFailurer interface {
	AuthFailure(ctx *LoginContext, info *auth_identity.Basic, err error)
}

type AuthInfor

type AuthInfor interface {
	AuthInfo(ctx *LoginContext, info *auth_identity.Basic) error
}

type AuthLoggeder

type AuthLoggeder interface {
	AuthLogged(ctx *LoginContext, claims *claims.Claims) error
}

type AuthURL

type AuthURL struct {
	Prefix string
	// contains filtered or unexported fields
}

func AuthURLFromContext

func AuthURLFromContext(context context.Context) *AuthURL

func (*AuthURL) LoginPage

func (a *AuthURL) LoginPage(redirect ...bool) (url string)

func (*AuthURL) LoginReturn

func (a *AuthURL) LoginReturn()

func (*AuthURL) LogoutPage

func (a *AuthURL) LogoutPage(redirect ...bool) (url string)

func (*AuthURL) Redirect

func (a *AuthURL) Redirect(url string)

type BeforeLoginCallback

type BeforeLoginCallback = func(ctx *LoginContext) error

type Config

type Config struct {
	*sites.SiteConfig
	// AuthIdentityModel a model used to save auth info, like email/password, OAuth token, linked user's BID, https://github.com/ecletus/auth/blob/master/auth_identity/auth_identity.go is the default implemention
	AuthIdentityModel interface{}
	// UserModel should be point of user struct's instance, it could be nil, then Auth will assume there is no user linked to auth info, and will return current auth info when get current user
	UserModel interface{}
	// Mount Auth into router with URLPrefix's value as prefix, default value is `/auth`.
	URLPrefix string

	// Auth is using [Render](https://github.com/ecletus/render) to render pages, you could configure it with your project's Render if you have advanced usage like [BindataFS](https://github.com/ecletus/bindatafs)
	Render *render.Render
	// Auth is using [NotifyMailer](https://github.com/ecletus/mailer) to send email, by default, it will print email into console, you need to configure it to send real one
	Mailer *mailer.Mailer
	// UserStorer is an interface that defined how to get/save user, Auth provides a default one based on AuthIdentityModel, UserModel's definition
	UserStorer UserStorerInterface
	// SessionStorer is an interface that defined how to encode/validate/save/destroy session data and flash messages between requests, Auth provides a default method do the job, to use the default value, don't forgot to mount SessionManager's middleware into your router to save session data correctly. refer [session](https://github.com/ecletus/session) for more details
	SessionStorer SessionStorerInterface
	// Redirector redirect user to a new page after registered, logged, confirmed...
	Redirector RedirectorInterface

	// LoginHandler defined behaviour when request `{Auth Prefix}/login`, default behaviour defined in http://godoc.org/github.com/ecletus/auth#pkg-variables
	LoginHandler func(*LoginContext, func(*LoginContext) (*claims.Claims, error)) (*claims.Claims, error)
	// RegisterHandler defined behaviour when request `{Auth Prefix}/register`, default behaviour defined in http://godoc.org/github.com/ecletus/auth#pkg-variables
	RegisterHandler func(*Context, func(*Context) (*claims.Claims, error))
	// LogoutHandler defined behaviour when request `{Auth Prefix}/logout`, default behaviour defined in http://godoc.org/github.com/ecletus/auth#pkg-variables
	LogoutHandler func(*Context)
	// ProfileHandler defined behaviour when request `{Auth Prefix}/profile`, default behaviour defined in http://godoc.org/github.com/ecletus/auth#pkg-variables
	ProfileHandler func(*Context)

	// RegistrableFunc Check if Allow register new users
	RegistrableFunc func(auth *Auth, ctx *core.Context) bool

	LoginPageRedirectTo string

	ContextFactory *core.ContextFactory

	LoginCallbacks LoginCallbacks

	UIDFinders UIDFinders
	// contains filtered or unexported fields
}

Config auth config

type Context

type Context struct {
	*core.Context
	*Auth
	Claims   *claims.Claims
	Provider Provider
}

Context context

func (*Context) AuthPath

func (context *Context) AuthPath(pth ...string) string

func (*Context) AuthStaticURL

func (context *Context) AuthStaticURL(pth ...string) string

func (*Context) AuthURL

func (context *Context) AuthURL(pth ...string) string

func (*Context) Flashes

func (context *Context) Flashes() []session.Message

Flashes get flash messages

func (*Context) FormValue

func (context *Context) FormValue(name string) string

FormValue get form value with name

func (*Context) GetContext

func (context *Context) GetContext() *core.Context

func (*Context) Registrable

func (context *Context) Registrable() bool

func (*Context) Set

func (context *Context) Set(values ...interface{})

type ErrorCallback

type ErrorCallback = func(ctx *LoginContext, err error)

type InfoCallback

type InfoCallback = func(ctx *LoginContext, info *auth_identity.Basic) error

type Interface

type Interface interface {
	GetCurrentUser(*Context) common.User
	LoginURL(*Context) string
	LogoutURL(*Context) string
	ProfileURL(c *Context) string
	Auth() *Auth
}

Interface is an auth interface that used to qor admin If you want to implement an authorization gateway for admin interface, you could implement this interface, and set it to the admin with `admin.SetAuth(auth)`

type LoggedCallback

type LoggedCallback = func(ctx *LoginContext, claims *claims.Claims) error

type LoginCallbacks

type LoginCallbacks struct {
	BeforeLoginCallbacks []BeforeLoginCallback
	InfoCallbacks        []InfoCallback
	LoggedCallbacks      []LoggedCallback
	ErrorCallbacks       []ErrorCallback
}

func (*LoginCallbacks) Before

func (cb *LoginCallbacks) Before(f ...func(ctx *LoginContext) error) *LoginCallbacks

func (*LoginCallbacks) Failure

func (cb *LoginCallbacks) Failure(f ...func(ctx *LoginContext, err error)) *LoginCallbacks

func (*LoginCallbacks) Info

func (cb *LoginCallbacks) Info(f ...func(ctx *LoginContext, info *auth_identity.Basic) error) *LoginCallbacks

func (*LoginCallbacks) Logged

func (cb *LoginCallbacks) Logged(f ...func(ctx *LoginContext, claims *claims.Claims) error) *LoginCallbacks

type LoginContext

type LoginContext struct {
	*Context
	LoginCallbacks

	UID         string
	LoginData   maps.Map
	Silent      bool
	SilentError bool
}

func (*LoginContext) Before

func (this *LoginContext) Before(f ...func(ctx *LoginContext) error) *LoginContext

func (*LoginContext) Failure

func (this *LoginContext) Failure(f ...func(ctx *LoginContext, err error)) *LoginContext

func (*LoginContext) Info

func (this *LoginContext) Info(f ...func(ctx *LoginContext, info *auth_identity.Basic) error) *LoginContext

func (*LoginContext) Logged

func (this *LoginContext) Logged(f ...func(ctx *LoginContext, claims *claims.Claims) error) *LoginContext

func (*LoginContext) Login

func (this *LoginContext) Login() (Claims *claims.Claims, err error)

type Plugin

type Plugin struct {
}

type Provider

type Provider interface {
	GetName() string

	ConfigAuth(*Auth)
	PrepareLoginContext(*LoginContext) error
	Login(*LoginContext) (*claims.Claims, error)
	Logout(*Context)
	Register(*Context)
	Callback(*Context)
	ServeHTTP(*Context)
	I18n(key ...string) string
}

Provider define Provider interface

type Redirector

type Redirector struct {
	*redirect_back.RedirectBack
}

Redirector default redirector

func (Redirector) Redirect

func (redirector Redirector) Redirect(w http.ResponseWriter, req *http.Request, action string)

Redirect redirect back after action

type RedirectorInterface

type RedirectorInterface interface {
	// Redirect redirect after action
	Redirect(w http.ResponseWriter, req *http.Request, action string)
	Middleware() *xroute.Middleware
}

RedirectorInterface redirector interface

type Schema

type Schema struct {
	Provider string
	UID      string

	Name      string
	Email     string
	FirstName string
	LastName  string
	Location  string
	Lang      []string
	Image     string
	Phone     string
	URL       string

	RawInfo interface{}
}

Schema auth schema

func (*Schema) MailAddress

func (this *Schema) MailAddress() *mail.Address

type SessionStorer

type SessionStorer struct {
	SessionName   string
	SigningMethod jwt.SigningMethod
	SignedString  string
}

SessionStorer default session storer

func (*SessionStorer) Delete

func (sessionStorer *SessionStorer) Delete(manager session.RequestSessionManager) error

Delete delete claims from session manager

func (*SessionStorer) Flash

func (sessionStorer *SessionStorer) Flash(manager session.RequestSessionManager, message session.Message) error

Flash add flash message to session data

func (*SessionStorer) Flashes

func (sessionStorer *SessionStorer) Flashes(manager session.RequestSessionManager) []session.Message

Flashes returns a slice of flash messages from session data

func (*SessionStorer) Get

func (sessionStorer *SessionStorer) Get(manager session.RequestSessionManager) (*claims.Claims, error)

Get get claims from request

func (*SessionStorer) SignedToken

func (sessionStorer *SessionStorer) SignedToken(claims *claims.Claims) (string, error)

SignedToken generate signed token with Claims

func (*SessionStorer) Update

func (sessionStorer *SessionStorer) Update(manager session.RequestSessionManager, claims *claims.Claims) (err error)

Update update claims with session manager

func (*SessionStorer) ValidateClaims

func (sessionStorer *SessionStorer) ValidateClaims(tokenString string) (*claims.Claims, error)

ValidateClaims validate auth token

type SessionStorerInterface

type SessionStorerInterface interface {
	// Get get claims from request
	Get(manager session.RequestSessionManager) (*claims.Claims, error)
	// Update update claims with session manager
	Update(manager session.RequestSessionManager, claims *claims.Claims) error
	// Delete delete session
	Delete(manager session.RequestSessionManager) error

	// Flash add flash message to session data
	Flash(manager session.RequestSessionManager, message session.Message) error
	// Flashes returns a slice of flash messages from session data
	Flashes(manager session.RequestSessionManager) []session.Message

	// SignedToken generate signed token with Claims
	SignedToken(claims *claims.Claims) (string, error)
	// ValidateClaims validate auth token
	ValidateClaims(tokenString string) (*claims.Claims, error)
}

SessionStorerInterface session storer interface for Auth

type SignedCallbacker

type SignedCallbacker interface {
	Signed(ctx *core.Context, claims *claims.Claims) error
}

type UIDFinder

type UIDFinder interface {
	FindUID(ctx *Context, identifier string) (uid string, err error)
}

type UIDFinders

type UIDFinders []UIDFinder

func (*UIDFinders) Add

func (this *UIDFinders) Add(finder ...UIDFinder)

func (UIDFinders) Append

func (this UIDFinders) Append(finder ...UIDFinder) UIDFinders

func (UIDFinders) FindUID

func (this UIDFinders) FindUID(ctx *Context, identifier string) (uid string, err error)

type User

type User interface {
	Schema() *Schema
}

type UserStorer

type UserStorer struct {
	FindFunc   func(context *Context, out interface{}, key aorm.ID) error
	CreateFunc func(context *Context, out interface{}) error
}

UserStorer default user storer

func (UserStorer) Get

func (us UserStorer) Get(Claims *claims.Claims, context *Context) (user User, err error)

Get defined how to get user with user id

func (UserStorer) Save

func (us UserStorer) Save(schema *Schema, context *Context) (user User, userID aorm.ID, err error)

Save defined how to save user

type UserStorerInterface

type UserStorerInterface interface {
	Save(schema *Schema, context *Context) (user User, userId aorm.ID, err error)
	Get(claims *claims.Claims, context *Context) (user User, err error)
}

UserStorerInterface user storer interface

Jump to

Keyboard shortcuts

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