Login via OpenID-2.0 (#618)
This commit is contained in:
committed by
Kim "BKC" Carlbäcker
parent
0693fbfc00
commit
71d16f69ff
21
cmd/web.go
21
cmd/web.go
@@ -200,6 +200,19 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Group("/user", func() {
|
||||
m.Get("/login", user.SignIn)
|
||||
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
|
||||
if setting.EnableOpenIDSignIn {
|
||||
m.Combo("/login/openid").
|
||||
Get(user.SignInOpenID).
|
||||
Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost)
|
||||
m.Group("/openid", func() {
|
||||
m.Combo("/connect").
|
||||
Get(user.ConnectOpenID).
|
||||
Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost)
|
||||
m.Combo("/register").
|
||||
Get(user.RegisterOpenID).
|
||||
Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost)
|
||||
})
|
||||
}
|
||||
m.Get("/sign_up", user.SignUp)
|
||||
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
|
||||
m.Get("/reset_password", user.ResetPasswd)
|
||||
@@ -230,6 +243,14 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Post("/email/delete", user.DeleteEmail)
|
||||
m.Get("/password", user.SettingsPassword)
|
||||
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||
if setting.EnableOpenIDSignIn {
|
||||
m.Group("/openid", func() {
|
||||
m.Combo("").Get(user.SettingsOpenID).
|
||||
Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost)
|
||||
m.Post("/delete", user.DeleteOpenID)
|
||||
})
|
||||
}
|
||||
|
||||
m.Combo("/ssh").Get(user.SettingsSSHKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
||||
m.Post("/ssh/delete", user.DeleteSSHKey)
|
||||
|
||||
32
conf/app.ini
vendored
32
conf/app.ini
vendored
@@ -182,6 +182,38 @@ MIN_PASSWORD_LENGTH = 6
|
||||
; True when users are allowed to import local server paths
|
||||
IMPORT_LOCAL_PATHS = false
|
||||
|
||||
[openid]
|
||||
;
|
||||
; OpenID is an open standard and decentralized authentication protocol.
|
||||
; Your identity is the address of a webpage you provide, which describes
|
||||
; how to prove you are in control of that page.
|
||||
;
|
||||
; For more info: https://en.wikipedia.org/wiki/OpenID
|
||||
;
|
||||
; Current implementation supports OpenID-2.0
|
||||
;
|
||||
; Tested to work providers at the time of writing:
|
||||
; - Any GNUSocial node (your.hostname.tld/username)
|
||||
; - Any SimpleID provider (http://simpleid.koinic.net)
|
||||
; - http://openid.org.cn/
|
||||
; - openid.stackexchange.com
|
||||
; - login.launchpad.net
|
||||
;
|
||||
; Whether to allow signin in via OpenID
|
||||
ENABLE_OPENID_SIGNIN = true
|
||||
; Whether to allow registering via OpenID
|
||||
ENABLE_OPENID_SIGNUP = true
|
||||
; Allowed URI patterns (POSIX regexp).
|
||||
; Space separated.
|
||||
; Only these would be allowed if non-blank.
|
||||
; Example value: trusted.domain.org trusted.domain.net
|
||||
WHITELISTED_URIS =
|
||||
; Forbidden URI patterns (POSIX regexp).
|
||||
; Space sepaated.
|
||||
; Only used if WHITELISTED_URIS is blank.
|
||||
; Example value: loadaverage.org/badguy stackexchange.com/.*spammer
|
||||
BLACKLISTED_URIS =
|
||||
|
||||
[service]
|
||||
ACTIVE_CODE_LIVE_MINUTES = 180
|
||||
RESET_PASSWD_CODE_LIVE_MINUTES = 180
|
||||
|
||||
@@ -93,6 +93,21 @@ func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
|
||||
type ErrOpenIDAlreadyUsed struct {
|
||||
OpenID string
|
||||
}
|
||||
|
||||
// IsErrOpenIDAlreadyUsed checks if an error is a ErrOpenIDAlreadyUsed.
|
||||
func IsErrOpenIDAlreadyUsed(err error) bool {
|
||||
_, ok := err.(ErrOpenIDAlreadyUsed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrOpenIDAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("OpenID has been used [oid: %s]", err.OpenID)
|
||||
}
|
||||
|
||||
// ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
|
||||
type ErrUserOwnRepos struct {
|
||||
UID int64
|
||||
|
||||
@@ -94,6 +94,8 @@ var migrations = []Migration{
|
||||
NewMigration("rewrite authorized_keys file via new format", useNewPublickeyFormat),
|
||||
// v22 -> v23
|
||||
NewMigration("generate and migrate wiki Git hooks", generateAndMigrateWikiGitHooks),
|
||||
// v23 -> v24
|
||||
NewMigration("add user openid table", addUserOpenID),
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
|
||||
26
models/migrations/v23.go
Normal file
26
models/migrations/v23.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Gitea. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
// UserOpenID is the list of all OpenID identities of a user.
|
||||
type UserOpenID struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX NOT NULL"`
|
||||
URI string `xorm:"UNIQUE NOT NULL"`
|
||||
}
|
||||
|
||||
|
||||
func addUserOpenID(x *xorm.Engine) error {
|
||||
if err := x.Sync2(new(UserOpenID)); err != nil {
|
||||
return fmt.Errorf("Sync2: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -116,6 +116,7 @@ func init() {
|
||||
new(RepoRedirect),
|
||||
new(ExternalLoginUser),
|
||||
new(ProtectedBranch),
|
||||
new(UserOpenID),
|
||||
)
|
||||
|
||||
gonicNames := []string{"SSL", "UID"}
|
||||
|
||||
@@ -964,6 +964,7 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||
&Action{UserID: u.ID},
|
||||
&IssueUser{UID: u.ID},
|
||||
&EmailAddress{UID: u.ID},
|
||||
&UserOpenID{UID: u.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
||||
117
models/user_openid.go
Normal file
117
models/user_openid.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"code.gitea.io/gitea/modules/auth/openid"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrOpenIDNotExist openid is not known
|
||||
ErrOpenIDNotExist = errors.New("OpenID is unknown")
|
||||
)
|
||||
|
||||
// UserOpenID is the list of all OpenID identities of a user.
|
||||
type UserOpenID struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX NOT NULL"`
|
||||
URI string `xorm:"UNIQUE NOT NULL"`
|
||||
}
|
||||
|
||||
// GetUserOpenIDs returns all openid addresses that belongs to given user.
|
||||
func GetUserOpenIDs(uid int64) ([]*UserOpenID, error) {
|
||||
openids := make([]*UserOpenID, 0, 5)
|
||||
if err := x.
|
||||
Where("uid=?", uid).
|
||||
Find(&openids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return openids, nil
|
||||
}
|
||||
|
||||
func isOpenIDUsed(e Engine, uri string) (bool, error) {
|
||||
if len(uri) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return e.Get(&UserOpenID{URI: uri})
|
||||
}
|
||||
|
||||
// IsOpenIDUsed returns true if the openid has been used.
|
||||
func IsOpenIDUsed(openid string) (bool, error) {
|
||||
return isOpenIDUsed(x, openid)
|
||||
}
|
||||
|
||||
// NOTE: make sure openid.URI is normalized already
|
||||
func addUserOpenID(e Engine, openid *UserOpenID) error {
|
||||
used, err := isOpenIDUsed(e, openid.URI)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if used {
|
||||
return ErrOpenIDAlreadyUsed{openid.URI}
|
||||
}
|
||||
|
||||
_, err = e.Insert(openid)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddUserOpenID adds an pre-verified/normalized OpenID URI to given user.
|
||||
func AddUserOpenID(openid *UserOpenID) error {
|
||||
return addUserOpenID(x, openid)
|
||||
}
|
||||
|
||||
// DeleteUserOpenID deletes an openid address of given user.
|
||||
func DeleteUserOpenID(openid *UserOpenID) (err error) {
|
||||
var deleted int64
|
||||
// ask to check UID
|
||||
var address = UserOpenID{
|
||||
UID: openid.UID,
|
||||
}
|
||||
if openid.ID > 0 {
|
||||
deleted, err = x.Id(openid.ID).Delete(&address)
|
||||
} else {
|
||||
deleted, err = x.
|
||||
Where("openid=?", openid.URI).
|
||||
Delete(&address)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if deleted != 1 {
|
||||
return ErrOpenIDNotExist
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByOpenID returns the user object by given OpenID if exists.
|
||||
func GetUserByOpenID(uri string) (*User, error) {
|
||||
if len(uri) == 0 {
|
||||
return nil, ErrUserNotExist{0, uri, 0}
|
||||
}
|
||||
|
||||
uri, err := openid.Normalize(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Trace("Normalized OpenID URI: " + uri)
|
||||
|
||||
// Otherwise, check in openid table
|
||||
oid := &UserOpenID{URI: uri}
|
||||
has, err := x.Get(oid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return GetUserByID(oid.UID)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{0, uri, 0}
|
||||
}
|
||||
|
||||
59
modules/auth/openid/discovery_cache.go
Normal file
59
modules/auth/openid/discovery_cache.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package openid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/yohcop/openid-go"
|
||||
)
|
||||
|
||||
type timedDiscoveredInfo struct {
|
||||
info openid.DiscoveredInfo
|
||||
time time.Time
|
||||
}
|
||||
|
||||
type timedDiscoveryCache struct {
|
||||
cache map[string]timedDiscoveredInfo
|
||||
ttl time.Duration
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func newTimedDiscoveryCache(ttl time.Duration) *timedDiscoveryCache {
|
||||
return &timedDiscoveryCache{cache: map[string]timedDiscoveredInfo{}, ttl: ttl, mutex: &sync.Mutex{}}
|
||||
}
|
||||
|
||||
func (s *timedDiscoveryCache) Put(id string, info openid.DiscoveredInfo) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.cache[id] = timedDiscoveredInfo{info: info, time: time.Now()}
|
||||
}
|
||||
|
||||
// Delete timed-out cache entries
|
||||
func (s *timedDiscoveryCache) cleanTimedOut() {
|
||||
now := time.Now()
|
||||
for k, e := range s.cache {
|
||||
diff := now.Sub(e.time)
|
||||
if diff > s.ttl {
|
||||
delete(s.cache, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *timedDiscoveryCache) Get(id string) openid.DiscoveredInfo {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
// Delete old cached while we are at it.
|
||||
s.cleanTimedOut()
|
||||
|
||||
if info, has := s.cache[id]; has {
|
||||
return info.info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
47
modules/auth/openid/discovery_cache_test.go
Normal file
47
modules/auth/openid/discovery_cache_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package openid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testDiscoveredInfo struct {}
|
||||
func (s *testDiscoveredInfo) ClaimedID() string {
|
||||
return "claimedID"
|
||||
}
|
||||
func (s *testDiscoveredInfo) OpEndpoint() string {
|
||||
return "opEndpoint"
|
||||
}
|
||||
func (s *testDiscoveredInfo) OpLocalID() string {
|
||||
return "opLocalID"
|
||||
}
|
||||
|
||||
func TestTimedDiscoveryCache(t *testing.T) {
|
||||
dc := newTimedDiscoveryCache(1*time.Second)
|
||||
|
||||
// Put some initial values
|
||||
dc.Put("foo", &testDiscoveredInfo{}) //openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"})
|
||||
|
||||
// Make sure we can retrieve them
|
||||
if di := dc.Get("foo"); di == nil {
|
||||
t.Errorf("Expected a result, got nil")
|
||||
} else if di.OpEndpoint() != "opEndpoint" || di.OpLocalID() != "opLocalID" || di.ClaimedID() != "claimedID" {
|
||||
t.Errorf("Expected opEndpoint opLocalID claimedID, got %v %v %v", di.OpEndpoint(), di.OpLocalID(), di.ClaimedID())
|
||||
}
|
||||
|
||||
// Attempt to get a non-existent value
|
||||
if di := dc.Get("bar"); di != nil {
|
||||
t.Errorf("Expected nil, got %v", di)
|
||||
}
|
||||
|
||||
// Sleep one second and try retrive again
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if di := dc.Get("foo"); di != nil {
|
||||
t.Errorf("Expected a nil, got a result")
|
||||
}
|
||||
}
|
||||
37
modules/auth/openid/openid.go
Normal file
37
modules/auth/openid/openid.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package openid
|
||||
|
||||
import (
|
||||
"github.com/yohcop/openid-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
// For the demo, we use in-memory infinite storage nonce and discovery
|
||||
// cache. In your app, do not use this as it will eat up memory and
|
||||
// never
|
||||
// free it. Use your own implementation, on a better database system.
|
||||
// If you have multiple servers for example, you may need to share at
|
||||
// least
|
||||
// the nonceStore between them.
|
||||
var nonceStore = openid.NewSimpleNonceStore()
|
||||
var discoveryCache = newTimedDiscoveryCache(24*time.Hour)
|
||||
|
||||
|
||||
// Verify handles response from OpenID provider
|
||||
func Verify(fullURL string) (id string, err error) {
|
||||
return openid.Verify(fullURL, discoveryCache, nonceStore)
|
||||
}
|
||||
|
||||
// Normalize normalizes an OpenID URI
|
||||
func Normalize(url string) (id string, err error) {
|
||||
return openid.Normalize(url)
|
||||
}
|
||||
|
||||
// RedirectURL redirects browser
|
||||
func RedirectURL(id, callbackURL, realm string) (string, error) {
|
||||
return openid.RedirectURL(id, callbackURL, realm)
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// SignInForm form for signing in
|
||||
// SignInForm form for signing in with user/password
|
||||
type SignInForm struct {
|
||||
UserName string `binding:"Required;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
@@ -153,6 +153,16 @@ func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors)
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AddOpenIDForm is for changing openid uri
|
||||
type AddOpenIDForm struct {
|
||||
Openid string `binding:"Required;MaxSize(256)"`
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AddOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AddSSHKeyForm form for adding SSH key
|
||||
type AddSSHKeyForm struct {
|
||||
Title string `binding:"Required;MaxSize(50)"`
|
||||
|
||||
45
modules/auth/user_form_auth_openid.go
Normal file
45
modules/auth/user_form_auth_openid.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
|
||||
// SignInOpenIDForm form for signing in with OpenID
|
||||
type SignInOpenIDForm struct {
|
||||
Openid string `binding:"Required;MaxSize(256)"`
|
||||
Remember bool
|
||||
}
|
||||
|
||||
// Validate valideates the fields
|
||||
func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// SignUpOpenIDForm form for signin up with OpenID
|
||||
type SignUpOpenIDForm struct {
|
||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
}
|
||||
|
||||
// Validate valideates the fields
|
||||
func (f *SignUpOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ConnectOpenIDForm form for connecting an existing account to an OpenID URI
|
||||
type ConnectOpenIDForm struct {
|
||||
UserName string `binding:"Required;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
}
|
||||
|
||||
// Validate valideates the fields
|
||||
func (f *ConnectOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ func Contexter() macaron.Handler {
|
||||
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
|
||||
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
|
||||
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
|
||||
ctx.Data["EnableOpenIDSignIn"] = setting.EnableOpenIDSignIn
|
||||
|
||||
c.Map(ctx)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -120,6 +121,12 @@ var (
|
||||
MinPasswordLength int
|
||||
ImportLocalPaths bool
|
||||
|
||||
// OpenID settings
|
||||
EnableOpenIDSignIn bool
|
||||
EnableOpenIDSignUp bool
|
||||
OpenIDWhitelist []*regexp.Regexp
|
||||
OpenIDBlacklist []*regexp.Regexp
|
||||
|
||||
// Database settings
|
||||
UseSQLite3 bool
|
||||
UseMySQL bool
|
||||
@@ -755,6 +762,24 @@ please consider changing to GITEA_CUSTOM`)
|
||||
MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6)
|
||||
ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
|
||||
|
||||
sec = Cfg.Section("openid")
|
||||
EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(true)
|
||||
EnableOpenIDSignUp = sec.Key("ENABLE_OPENID_SIGNUP").MustBool(true)
|
||||
pats := sec.Key("WHITELISTED_URIS").Strings(" ")
|
||||
if ( len(pats) != 0 ) {
|
||||
OpenIDWhitelist = make([]*regexp.Regexp, len(pats))
|
||||
for i, p := range pats {
|
||||
OpenIDWhitelist[i] = regexp.MustCompilePOSIX(p)
|
||||
}
|
||||
}
|
||||
pats = sec.Key("BLACKLISTED_URIS").Strings(" ")
|
||||
if ( len(pats) != 0 ) {
|
||||
OpenIDBlacklist = make([]*regexp.Regexp, len(pats))
|
||||
for i, p := range pats {
|
||||
OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p)
|
||||
}
|
||||
}
|
||||
|
||||
sec = Cfg.Section("attachment")
|
||||
AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments"))
|
||||
if !filepath.IsAbs(AttachmentPath) {
|
||||
|
||||
@@ -188,6 +188,14 @@ use_scratch_code = Use a scratch code
|
||||
twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code.
|
||||
twofa_passcode_incorrect = Your passcode is not correct. If you misplaced your device, use your scratch code to login.
|
||||
twofa_scratch_token_incorrect = Your scratch code is not correct.
|
||||
login_userpass = User / Password
|
||||
login_openid = OpenID
|
||||
openid_connect_submit = Connect
|
||||
openid_connect_title = Connect to an existing account
|
||||
openid_connect_desc = The entered OpenID URIs is not know by the system, here you can associate it to an existing account.
|
||||
openid_register_title = Create new account
|
||||
openid_register_desc = The entered OpenID URIs is not know by the system, here you can associate it to a new account.
|
||||
openid_signin_desc = Example URIs: https://anne.me, bob.openid.org.cn, gnusocial.net/carry
|
||||
|
||||
[mail]
|
||||
activate_account = Please activate your account
|
||||
@@ -239,6 +247,7 @@ repo_name_been_taken = Repository name has already been used.
|
||||
org_name_been_taken = Organization name has already been taken.
|
||||
team_name_been_taken = Team name has already been taken.
|
||||
email_been_used = Email address has already been used.
|
||||
openid_been_used = OpenID address '%s' has already been used.
|
||||
username_password_incorrect = Username or password is not correct.
|
||||
enterred_invalid_repo_name = Please make sure that the repository name you entered is correct.
|
||||
enterred_invalid_owner_name = Please make sure that the owner name you entered is correct.
|
||||
@@ -315,6 +324,7 @@ password_change_disabled = Non-local users are not allowed to change their passw
|
||||
|
||||
emails = Email Addresses
|
||||
manage_emails = Manage email addresses
|
||||
manage_openid = Manage OpenID addresses
|
||||
email_desc = Your primary email address will be used for notifications and other operations.
|
||||
primary = Primary
|
||||
primary_email = Set as primary
|
||||
@@ -322,12 +332,19 @@ delete_email = Delete
|
||||
email_deletion = Email Deletion
|
||||
email_deletion_desc = Deleting this email address will remove all related information from your account. Do you want to continue?
|
||||
email_deletion_success = Email has been deleted successfully!
|
||||
openid_deletion = OpenID Deletion
|
||||
openid_deletion_desc = Deleting this OpenID address will prevent you from signing in using it, are you sure you want to continue ?
|
||||
openid_deletion_success = OpenID has been deleted successfully!
|
||||
add_new_email = Add new email address
|
||||
add_new_openid = Add new OpenID URI
|
||||
add_email = Add email
|
||||
add_openid = Add OpenID URI
|
||||
add_email_confirmation_sent = A new confirmation email has been sent to '%s', please check your inbox within the next %d hours to complete the confirmation process.
|
||||
add_email_success = Your new email address was successfully added.
|
||||
add_openid_success = Your new OpenID address was successfully added.
|
||||
keep_email_private = Keep Email Address Private
|
||||
keep_email_private_popup = Your email address will be hidden from other users if this option is set.
|
||||
openid_desc = Your OpenID addresses will let you delegate authentication to your provider of choice
|
||||
|
||||
manage_ssh_keys = Manage SSH Keys
|
||||
add_key = Add Key
|
||||
|
||||
BIN
public/img/openid-16x16.png
Normal file
BIN
public/img/openid-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 B |
@@ -107,7 +107,6 @@ func checkAutoLogin(ctx *context.Context) bool {
|
||||
|
||||
// SignIn render sign in page
|
||||
func SignIn(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
||||
|
||||
// Check auto-login.
|
||||
if checkAutoLogin(ctx) {
|
||||
@@ -120,6 +119,9 @@ func SignIn(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["OAuth2Providers"] = oauth2Providers
|
||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsLogin"] = true
|
||||
|
||||
ctx.HTML(200, tplSignIn)
|
||||
}
|
||||
@@ -127,6 +129,8 @@ func SignIn(ctx *context.Context) {
|
||||
// SignInPost response for sign in request
|
||||
func SignInPost(ctx *context.Context, form auth.SignInForm) {
|
||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsLogin"] = true
|
||||
|
||||
oauth2Providers, err := models.GetActiveOAuth2Providers()
|
||||
if err != nil {
|
||||
@@ -316,6 +320,10 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
|
||||
setting.CookieRememberName, u.Name, days, setting.AppSubURL)
|
||||
}
|
||||
|
||||
ctx.Session.Delete("openid_verified_uri")
|
||||
ctx.Session.Delete("openid_signin_remember")
|
||||
ctx.Session.Delete("openid_determined_email")
|
||||
ctx.Session.Delete("openid_determined_username")
|
||||
ctx.Session.Delete("twofaUid")
|
||||
ctx.Session.Delete("twofaRemember")
|
||||
ctx.Session.Set("uid", u.ID)
|
||||
|
||||
426
routers/user/auth_openid.go
Normal file
426
routers/user/auth_openid.go
Normal file
File diff suppressed because it is too large
Load Diff
142
routers/user/setting_openid.go
Normal file
142
routers/user/setting_openid.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/auth"
|
||||
"code.gitea.io/gitea/modules/auth/openid"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
tplSettingsOpenID base.TplName = "user/settings/openid"
|
||||
)
|
||||
|
||||
// SettingsOpenID renders change user's openid page
|
||||
func SettingsOpenID(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsOpenID"] = true
|
||||
|
||||
if ctx.Query("openid.return_to") != "" {
|
||||
settingsOpenIDVerify(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
openid, err := models.GetUserOpenIDs(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetUserOpenIDs", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["OpenIDs"] = openid
|
||||
|
||||
ctx.HTML(200, tplSettingsOpenID)
|
||||
}
|
||||
|
||||
// SettingsOpenIDPost response for change user's openid
|
||||
func SettingsOpenIDPost(ctx *context.Context, form auth.AddOpenIDForm) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsOpenID"] = true
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, tplSettingsOpenID)
|
||||
return
|
||||
}
|
||||
|
||||
// WARNING: specifying a wrong OpenID here could lock
|
||||
// a user out of her account, would be better to
|
||||
// verify/confirm the new OpenID before storing it
|
||||
|
||||
// Also, consider allowing for multiple OpenID URIs
|
||||
|
||||
id, err := openid.Normalize(form.Openid)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSettingsOpenID, &form)
|
||||
return;
|
||||
}
|
||||
form.Openid = id
|
||||
log.Trace("Normalized id: " + id)
|
||||
|
||||
oids, err := models.GetUserOpenIDs(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetUserOpenIDs", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["OpenIDs"] = oids
|
||||
|
||||
// Check that the OpenID is not already used
|
||||
for _, obj := range oids {
|
||||
if obj.URI == id {
|
||||
ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsOpenID, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
redirectTo := setting.AppURL + "user/settings/openid"
|
||||
url, err := openid.RedirectURL(id, redirectTo, setting.AppURL)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSettingsOpenID, &form)
|
||||
return;
|
||||
}
|
||||
ctx.Redirect(url)
|
||||
}
|
||||
|
||||
func settingsOpenIDVerify(ctx *context.Context) {
|
||||
log.Trace("Incoming call to: " + ctx.Req.Request.URL.String())
|
||||
|
||||
fullURL := setting.AppURL + ctx.Req.Request.URL.String()[1:]
|
||||
log.Trace("Full URL: " + fullURL)
|
||||
|
||||
oids, err := models.GetUserOpenIDs(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetUserOpenIDs", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["OpenIDs"] = oids
|
||||
|
||||
id, err := openid.Verify(fullURL)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSettingsOpenID, &auth.AddOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Verified ID: " + id)
|
||||
|
||||
oid := &models.UserOpenID{UID:ctx.User.ID, URI:id}
|
||||
if err = models.AddUserOpenID(oid); err != nil {
|
||||
if models.IsErrOpenIDAlreadyUsed(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.openid_been_used", id), tplSettingsOpenID, &auth.AddOpenIDForm{ Openid: id })
|
||||
return
|
||||
}
|
||||
ctx.Handle(500, "AddUserOpenID", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Associated OpenID %s to user %s", id, ctx.User.Name)
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/openid")
|
||||
}
|
||||
|
||||
// DeleteOpenID response for delete user's openid
|
||||
func DeleteOpenID(ctx *context.Context) {
|
||||
if err := models.DeleteUserOpenID(&models.UserOpenID{ID: ctx.QueryInt64("id"), UID: ctx.User.ID}); err != nil {
|
||||
ctx.Handle(500, "DeleteUserOpenID", err)
|
||||
return
|
||||
}
|
||||
log.Trace("OpenID address deleted: %s", ctx.User.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success"))
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/openid",
|
||||
})
|
||||
}
|
||||
|
||||
46
templates/user/auth/finalize_openid.tmpl
Normal file
46
templates/user/auth/finalize_openid.tmpl
Normal file
@@ -0,0 +1,46 @@
|
||||
{{template "base/head" .}}
|
||||
<div class="user signin">
|
||||
<div class="ui container">
|
||||
<div class="ui grid">
|
||||
{{template "user/auth/finalize_openid_navbar" .}}
|
||||
<div class="twelve wide column content">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "auth.login_userpass"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required inline field {{if .Err_UserName}}error{{end}}">
|
||||
<label for="user_name">{{.i18n.Tr "home.uname_holder"}}</label>
|
||||
<input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required inline field {{if .Err_Password}}error{{end}}">
|
||||
<label for="password">{{.i18n.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<div class="ui checkbox">
|
||||
<label>{{.i18n.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui green button">{{.i18n.Tr "sign_in"}}</button>
|
||||
<a href="{{AppSubUrl}}/user/forget_password">{{.i18n.Tr "auth.forget_password"}}</a>
|
||||
</div>
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<a href="{{AppSubUrl}}/user/sign_up">{{.i18n.Tr "auth.sign_up_now" | Str2html}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
@@ -1,3 +1,8 @@
|
||||
{{template "base/head" .}}
|
||||
{{template "user/auth/signin_inner" .}}
|
||||
<div class="user signin{{if .LinkAccountMode}} icon{{end}}">
|
||||
{{template "user/auth/signin_navbar" .}}
|
||||
<div class="ui container">
|
||||
{{template "user/auth/signin_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
||||
@@ -1,57 +1,51 @@
|
||||
<div class="user signin{{if .LinkAccountMode}} icon{{end}}">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "auth.login_userpass"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form" action="{{if not .LinkAccountMode}}{{.Link}}{{else}}{{.SignInLink}}{{end}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<h3 class="ui top attached header">
|
||||
{{.i18n.Tr "sign_in"}}
|
||||
</h3>
|
||||
<div class="ui attached segment">
|
||||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
<div class="required inline field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="user_name">{{.i18n.Tr "home.uname_holder"}}</label>
|
||||
<input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="password">{{.i18n.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
||||
</div>
|
||||
{{if not .LinkAccountMode}}
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<div class="ui checkbox">
|
||||
<label>{{.i18n.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui green button">{{.i18n.Tr "sign_in"}}</button>
|
||||
<a href="{{AppSubUrl}}/user/forgot_password">{{.i18n.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<a href="{{AppSubUrl}}/user/sign_up">{{.i18n.Tr "auth.sign_up_now" | Str2html}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .OAuth2Providers}}
|
||||
<div class="ui attached segment">
|
||||
<div class="oauth2 center">
|
||||
<div>
|
||||
<p>{{.i18n.Tr "sign_in_with"}}</p>{{range $key, $value := .OAuth2Providers}}<a href="{{AppSubUrl}}/user/oauth2/{{$key}}"><img alt="{{$value.DisplayName}}" title="{{$value.DisplayName}}" src="{{AppSubUrl}}{{$value.Image}}"></a>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required inline field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="user_name">{{.i18n.Tr "home.uname_holder"}}</label>
|
||||
<input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="password">{{.i18n.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
||||
</div>
|
||||
{{if not .LinkAccountMode}}
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<div class="ui checkbox">
|
||||
<label>{{.i18n.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui green button">{{.i18n.Tr "sign_in"}}</button>
|
||||
<a href="{{AppSubUrl}}/user/forgot_password">{{.i18n.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<a href="{{AppSubUrl}}/user/sign_up">{{.i18n.Tr "auth.sign_up_now" | Str2html}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .OAuth2Providers}}
|
||||
<div class="ui attached segment">
|
||||
<div class="oauth2 center">
|
||||
<div>
|
||||
<p>{{.i18n.Tr "sign_in_with"}}</p>{{range $key, $value := .OAuth2Providers}}<a href="{{AppSubUrl}}/user/oauth2/{{$key}}"><img alt="{{$value.DisplayName}}" title="{{$value.DisplayName}}" src="{{AppSubUrl}}{{$value.Image}}"></a>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
11
templates/user/auth/signin_navbar.tmpl
Normal file
11
templates/user/auth/signin_navbar.tmpl
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar">
|
||||
<a class="{{if .PageIsLogin}}active{{end}} item" href="{{AppSubUrl}}/user/login">
|
||||
{{.i18n.Tr "auth.login_userpass"}}
|
||||
</a>
|
||||
{{if .EnableOpenIDSignIn}}
|
||||
<a class="{{if .PageIsLoginOpenID}}active{{end}} item" href="{{AppSubUrl}}/user/login/openid">
|
||||
<img align="left" width="16" height="16" src="{{AppSubUrl}}/img/openid-16x16.png"/>
|
||||
OpenID
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
37
templates/user/auth/signin_openid.tmpl
Normal file
37
templates/user/auth/signin_openid.tmpl
Normal file
@@ -0,0 +1,37 @@
|
||||
{{template "base/head" .}}
|
||||
<div class="user signin openid">
|
||||
{{template "user/auth/signin_navbar" .}}
|
||||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
OpenID
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="inline field">
|
||||
{{.i18n.Tr "auth.openid_signin_desc"}}
|
||||
</div>
|
||||
<div class="required inline field {{if .Err_OpenID}}error{{end}}">
|
||||
<label for="openid">
|
||||
<img alt="OpenID URI" height="16" src="{{AppSubUrl}}/img/openid-16x16.png"/>
|
||||
OpenID URI
|
||||
</label>
|
||||
<input id="openid" name="openid" value="{{.openid}}" autofocus required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<div class="ui checkbox">
|
||||
<label>{{.i18n.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui green button">{{.i18n.Tr "sign_in"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
45
templates/user/auth/signup_openid_connect.tmpl
Normal file
45
templates/user/auth/signup_openid_connect.tmpl
Normal file
@@ -0,0 +1,45 @@
|
||||
{{template "base/head" .}}
|
||||
<div class="user signup">
|
||||
{{template "user/auth/signup_openid_navbar" .}}
|
||||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "auth.openid_connect_title"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>
|
||||
{{.i18n.Tr "auth.openid_connect_desc"}}
|
||||
</p>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required inline field {{if .Err_UserName}}error{{end}}">
|
||||
<label for="user_name">{{.i18n.Tr "home.uname_holder"}}</label>
|
||||
<input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required inline field {{if .Err_Password}}error{{end}}">
|
||||
<label for="password">{{.i18n.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
OpenID: {{ .OpenID }}
|
||||
</div>
|
||||
{{if .EnableCaptcha}}
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
{{.Captcha.CreateHtml}}
|
||||
</div>
|
||||
<div class="required inline field {{if .Err_Captcha}}error{{end}}">
|
||||
<label for="captcha">{{.i18n.Tr "captcha"}}</label>
|
||||
<input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui green button">{{.i18n.Tr "auth.openid_connect_submit"}}</button>
|
||||
<a href="{{AppSubUrl}}/user/forgot_password">{{.i18n.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
11
templates/user/auth/signup_openid_navbar.tmpl
Normal file
11
templates/user/auth/signup_openid_navbar.tmpl
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar">
|
||||
<a class="{{if .PageIsOpenIDConnect}}active{{end}} item" href="{{AppSubUrl}}/user/openid/connect">
|
||||
{{.i18n.Tr "auth.openid_connect_title"}}
|
||||
</a>
|
||||
{{if .EnableOpenIDSignUp}}
|
||||
<a class="{{if .PageIsOpenIDRegister}}active{{end}} item" href="{{AppSubUrl}}/user/openid/register">
|
||||
{{.i18n.Tr "auth.openid_register_title"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
34
templates/user/auth/signup_openid_register.tmpl
Normal file
34
templates/user/auth/signup_openid_register.tmpl
Normal file
@@ -0,0 +1,34 @@
|
||||
{{template "base/head" .}}
|
||||
<div class="user signup">
|
||||
{{template "user/auth/signup_openid_navbar" .}}
|
||||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "auth.openid_register_title"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>
|
||||
{{.i18n.Tr "auth.openid_register_desc"}}
|
||||
</p>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required inline field {{if .Err_UserName}}error{{end}}">
|
||||
<label for="user_name">{{.i18n.Tr "username"}}</label>
|
||||
<input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required inline field {{if .Err_Email}}error{{end}}">
|
||||
<label for="email">{{.i18n.Tr "email"}}</label>
|
||||
<input id="email" name="email" type="email" value="{{.email}}" required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
OpenID: {{ .OpenID }}
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui green button">{{.i18n.Tr "auth.create_new_account"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
@@ -11,6 +11,11 @@
|
||||
<a class="{{if .PageIsSettingsEmails}}active{{end}} item" href="{{AppSubUrl}}/user/settings/email">
|
||||
{{.i18n.Tr "settings.emails"}}
|
||||
</a>
|
||||
{{if .EnableOpenIDSignIn}}
|
||||
<a class="{{if .PageIsSettingsOpenID}}active{{end}} item" href="{{AppSubUrl}}/user/settings/openid">
|
||||
OpenID
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="{{if .PageIsSettingsSSHKeys}}active{{end}} item" href="{{AppSubUrl}}/user/settings/ssh">
|
||||
{{.i18n.Tr "settings.ssh_keys"}}
|
||||
</a>
|
||||
@@ -26,4 +31,4 @@
|
||||
<a class="{{if .PageIsSettingsDelete}}active{{end}} item" href="{{AppSubUrl}}/user/settings/delete">
|
||||
{{.i18n.Tr "settings.delete"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
57
templates/user/settings/openid.tmpl
Normal file
57
templates/user/settings/openid.tmpl
Normal file
@@ -0,0 +1,57 @@
|
||||
{{template "base/head" .}}
|
||||
<div class="user settings openid">
|
||||
<div class="ui container">
|
||||
<div class="ui grid">
|
||||
{{template "user/settings/navbar" .}}
|
||||
<div class="twelve wide column content">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "settings.manage_openid"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<div class="ui openid list">
|
||||
<div class="item">
|
||||
{{.i18n.Tr "settings.openid_desc"}}
|
||||
</div>
|
||||
{{range .OpenIDs}}
|
||||
<div class="item ui grid">
|
||||
<div class="column">
|
||||
<strong>{{.URI}}</strong>
|
||||
<div class="ui right">
|
||||
<button class="ui red tiny button delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
|
||||
{{$.i18n.Tr "settings.delete_key"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui attached bottom segment">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if .Err_OpenID}}error{{end}}">
|
||||
<label for="openid">{{.i18n.Tr "settings.add_new_openid"}}</label>
|
||||
<input id="openid" name="openid" type="openid" autofocus required>
|
||||
</div>
|
||||
<button class="ui green button">
|
||||
{{.i18n.Tr "settings.add_openid"}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui small basic delete modal">
|
||||
<div class="ui icon header">
|
||||
<i class="trash icon"></i>
|
||||
{{.i18n.Tr "settings.openid_deletion"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{.i18n.Tr "settings.openid_deletion_desc"}}</p>
|
||||
</div>
|
||||
{{template "base/delete_modal_actions" .}}
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user