feat: Login, OP Support and Auth Queries (#177)
* fix: change oidc config * fix: change oidc config secret * begin models * begin repo * fix: implement grpc app funcs * fix: add application requests * fix: converter * fix: converter * fix: converter and generate clientid * fix: tests * feat: project grant aggregate * feat: project grant * fix: project grant check if role existing * fix: project grant requests * fix: project grant fixes * fix: project grant member model * fix: project grant member aggregate * fix: project grant member eventstore * fix: project grant member requests * feat: user model * begin repo * repo models and more * feat: user command side * lots of functions * user command side * profile requests * commit before rebase on user * save * local config with gopass and more * begin new auth command (user centric) * Update internal/user/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/user_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/eventstore_mock_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * changes from mr review * save files into basedir * changes from mr review * changes from mr review * move to auth request * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * changes requested on mr * fix generate codes * fix return if no events * password code * email verification step * more steps * lot of mfa * begin tests * more next steps * auth api * auth api (user) * auth api (user) * auth api (user) * differ requests * merge * tests * fix compilation error * mock for id generator * Update internal/user/repository/eventsourcing/model/password.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * requests of mr * check email * begin separation of command and query * otp * change packages * some cleanup and fixes * tests for auth request / next steps * add VerificationLifetimes to config and make it run * tests * fix code challenge validation * cleanup * fix merge * begin view * repackaging tests and configs * fix startup config for auth * add migration * add PromptSelectAccount * fix copy / paste * remove user_agent files * fixes * fix sequences in user_session * token commands * token queries and signout * fix * fix set password test * add token handler and table * handle session init * add session state * add user view test cases * change VerifyMyMfaOTP * some fixes * fix user repo in auth api * cleanup * add user session view test * fix merge * begin oidc * user agent and more * config * keys * key command and query * add login statics * key handler * start login * login handlers * lot of fixes * merge oidc * add missing exports * add missing exports * fix some bugs * authrequestid in htmls * getrequest * update auth request * fix userid check * add username to authrequest * fix user session and auth request handling * fix UserSessionsByAgentID * fix auth request tests * fix user session on UserPasswordChanged and MfaOtpRemoved * fix MfaTypesSetupPossible * handle mfa * fill username * auth request query checks new events * fix userSessionByIDs * fix tokens * fix userSessionByIDs test * add user selection * init code * user code creation date * add init user step * add verification failed types * add verification failures * verify init code * user init code handle * user init code handle * fix userSessionByIDs * update logging * user agent cookie * browserinfo from request * add DeleteAuthRequest * add static login files to binary * add login statik to build * move generate to separate file and remove statik.go files * remove static dirs from startup.yaml * generate into separate namespaces * merge master * auth request code * auth request type mapping * fix keys * improve tokens * improve register and basic styling * fix ailerons font * improve password reset * add audience to token * all oidc apps as audience * fix test nextStep * fix email texts * remove "not set" * lot of style changes * improve copy to clipboard * fix footer * add cookie handler * remove placeholders * fix compilation after merge * fix auth config * remove comments * typo * use new secrets store * change default pws to match default policy * fixes * add todo * enable login * fix db name * Auth queries (#179) * my usersession * org structure/ auth handlers * working user grant spooler * auth internal user grants * search my project orgs * remove permissions file * my zitadel permissions * my zitadel permissions * remove unused code * authz * app searches in view * token verification * fix user grant load * fix tests * fix tests * read configs * remove unused const * remove todos * env variables * app_name * working authz * search projects * global resourceowner * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * model2 rename * at least it works * check token expiry * search my user grants * remove token table from authz Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix test * fix ports and enable console Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Silvan <silvan.reusser@gmail.com>
@@ -1,4 +0,0 @@
|
||||
package login
|
||||
|
||||
type Config struct {
|
||||
}
|
||||
27
internal/login/handler/auth_request.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
queryAuthRequestID = "authRequestID"
|
||||
)
|
||||
|
||||
func (l *Login) getAuthRequest(r *http.Request) (*model.AuthRequest, error) {
|
||||
authRequestID := r.FormValue(queryAuthRequestID)
|
||||
if authRequestID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return l.authRepo.AuthRequestByID(r.Context(), authRequestID)
|
||||
}
|
||||
|
||||
func (l *Login) getAuthRequestAndParseData(r *http.Request, data interface{}) (*model.AuthRequest, error) {
|
||||
authReq, err := l.getAuthRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = l.parser.Parse(r, data)
|
||||
return authReq, err
|
||||
}
|
||||
11
internal/login/handler/callback_handler.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||
callback := l.oidcAuthCallbackURL + authReq.ID
|
||||
http.Redirect(w, r, callback, http.StatusFound)
|
||||
}
|
||||
52
internal/login/handler/change_password_handler.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplChangePassword = "changepassword"
|
||||
tmplChangePasswordDone = "changepassworddone"
|
||||
)
|
||||
|
||||
type changePasswordData struct {
|
||||
OldPassword string `schema:"old_password"`
|
||||
NewPassword string `schema:"new_password"`
|
||||
}
|
||||
|
||||
func (l *Login) handleChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(changePasswordData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
err = l.authRepo.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.OldPassword, data.NewPassword)
|
||||
if err != nil {
|
||||
l.renderChangePassword(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.renderChangePasswordDone(w, r, authReq)
|
||||
}
|
||||
|
||||
func (l *Login) renderChangePassword(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "Change Password", errType, errMessage),
|
||||
UserName: authReq.UserName,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplChangePassword], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) renderChangePasswordDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||
var errType, errMessage string
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "Password Change Done", errType, errMessage),
|
||||
UserName: authReq.UserName,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplChangePasswordDone], data, nil)
|
||||
}
|
||||
18
internal/login/handler/health_handler.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (l *Login) handleHealthz(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
func (l *Login) handleReadiness(w http.ResponseWriter, r *http.Request) {
|
||||
err := l.authRepo.Health(r.Context())
|
||||
if err != nil {
|
||||
http.Error(w, "not ready", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
97
internal/login/handler/init_password_handler.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
queryInitPWCode = "code"
|
||||
queryInitPWUserID = "userID"
|
||||
|
||||
tmplInitPassword = "initpassword"
|
||||
tmplInitPasswordDone = "initpassworddone"
|
||||
)
|
||||
|
||||
type initPasswordFormData struct {
|
||||
Code string `schema:"code"`
|
||||
Password string `schema:"password"`
|
||||
PasswordConfirm string `schema:"passwordconfirm"`
|
||||
UserID string `schema:"userID"`
|
||||
Resend bool `schema:"resend"`
|
||||
}
|
||||
|
||||
type initPasswordData struct {
|
||||
baseData
|
||||
Code string
|
||||
UserID string
|
||||
}
|
||||
|
||||
func (l *Login) handleInitPassword(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.FormValue(queryInitPWUserID)
|
||||
code := r.FormValue(queryInitPWCode)
|
||||
l.renderInitPassword(w, r, nil, userID, code, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleInitPasswordCheck(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(initPasswordFormData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
|
||||
if data.Resend {
|
||||
l.resendPasswordSet(w, r, authReq)
|
||||
return
|
||||
}
|
||||
l.checkPWCode(w, r, authReq, data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *initPasswordFormData, err error) {
|
||||
if data.Password != data.PasswordConfirm {
|
||||
err := errors.ThrowInvalidArgument(nil, "VIEW-KaGue", "passwords dont match")
|
||||
l.renderInitPassword(w, r, authReq, data.UserID, data.Code, err)
|
||||
return
|
||||
}
|
||||
userOrg := login
|
||||
if authReq != nil {
|
||||
userOrg = authReq.UserOrgID
|
||||
}
|
||||
err = l.authRepo.SetPassword(setContext(r.Context(), userOrg), data.UserID, data.Code, data.Password)
|
||||
if err != nil {
|
||||
l.renderInitPassword(w, r, authReq, data.UserID, "", err)
|
||||
return
|
||||
}
|
||||
l.renderInitPasswordDone(w, r, authReq)
|
||||
}
|
||||
|
||||
func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||
err := l.authRepo.RequestPasswordReset(r.Context(), authReq.UserName)
|
||||
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
||||
}
|
||||
|
||||
func (l *Login) renderInitPassword(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
if userID == "" && authReq != nil {
|
||||
userID = authReq.UserID
|
||||
}
|
||||
data := initPasswordData{
|
||||
baseData: l.getBaseData(r, authReq, "Init Password", errType, errMessage),
|
||||
UserID: userID,
|
||||
Code: code,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitPassword], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) renderInitPasswordDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "Password Init Done", "", ""),
|
||||
UserName: authReq.UserName,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitPasswordDone], data, nil)
|
||||
}
|
||||
105
internal/login/handler/init_user_handler.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
queryInitUserCode = "code"
|
||||
queryInitUserUserID = "userID"
|
||||
|
||||
tmplInitUser = "inituser"
|
||||
tmplInitUserDone = "inituserdone"
|
||||
)
|
||||
|
||||
type initUserFormData struct {
|
||||
Code string `schema:"code"`
|
||||
Password string `schema:"password"`
|
||||
PasswordConfirm string `schema:"passwordconfirm"`
|
||||
UserID string `schema:"userID"`
|
||||
Resend bool `schema:"resend"`
|
||||
}
|
||||
|
||||
type initUserData struct {
|
||||
baseData
|
||||
Code string
|
||||
UserID string
|
||||
}
|
||||
|
||||
func (l *Login) handleInitUser(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.FormValue(queryInitUserUserID)
|
||||
code := r.FormValue(queryInitUserCode)
|
||||
l.renderInitUser(w, r, nil, userID, code, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleInitUserCheck(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(initUserFormData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
if data.Resend {
|
||||
l.resendUserInit(w, r, authReq, data.UserID)
|
||||
return
|
||||
}
|
||||
l.checkUserInitCode(w, r, authReq, data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *initUserFormData, err error) {
|
||||
if data.Password != data.PasswordConfirm {
|
||||
err := caos_errs.ThrowInvalidArgument(nil, "VIEW-fsdfd", "passwords dont match")
|
||||
l.renderInitUser(w, r, nil, data.UserID, data.Code, err)
|
||||
return
|
||||
}
|
||||
userOrgID := login
|
||||
if authReq != nil {
|
||||
userOrgID = authReq.UserOrgID
|
||||
}
|
||||
err = l.authRepo.VerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, data.Code, data.Password)
|
||||
if err != nil {
|
||||
l.renderInitUser(w, r, nil, data.UserID, "", err)
|
||||
return
|
||||
}
|
||||
l.renderInitUserDone(w, r, nil)
|
||||
}
|
||||
|
||||
func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID string) {
|
||||
userOrgID := login
|
||||
if authReq != nil {
|
||||
userOrgID = authReq.UserOrgID
|
||||
}
|
||||
err := l.authRepo.ResendInitVerificationMail(setContext(r.Context(), userOrgID), userID)
|
||||
l.renderInitUser(w, r, authReq, userID, "", err)
|
||||
}
|
||||
|
||||
func (l *Login) renderInitUser(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
if authReq != nil {
|
||||
userID = authReq.UserID
|
||||
}
|
||||
data := initUserData{
|
||||
baseData: l.getBaseData(r, nil, "Init User", errType, errMessage),
|
||||
UserID: userID,
|
||||
Code: code,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitUser], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) renderInitUserDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||
var errType, errMessage, userName string
|
||||
if authReq != nil {
|
||||
userName = authReq.UserName
|
||||
}
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "User Init Done", errType, errMessage),
|
||||
UserName: userName,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplInitUserDone], data, nil)
|
||||
}
|
||||
92
internal/login/handler/login.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rakyll/statik/fs"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
|
||||
"github.com/caos/zitadel/internal/form"
|
||||
|
||||
_ "github.com/caos/zitadel/internal/login/statik"
|
||||
)
|
||||
|
||||
type Login struct {
|
||||
endpoint string
|
||||
router *mux.Router
|
||||
renderer *Renderer
|
||||
parser *form.Parser
|
||||
authRepo *eventsourcing.EsRepository
|
||||
zitadelURL string
|
||||
oidcAuthCallbackURL string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Port string
|
||||
OidcAuthCallbackURL string
|
||||
ZitadelURL string
|
||||
LanguageCookieName string
|
||||
DefaultLanguage language.Tag
|
||||
}
|
||||
|
||||
const (
|
||||
login = "LOGIN"
|
||||
)
|
||||
|
||||
func StartLogin(ctx context.Context, config Config, authRepo *eventsourcing.EsRepository) {
|
||||
login := &Login{
|
||||
endpoint: config.Port,
|
||||
oidcAuthCallbackURL: config.OidcAuthCallbackURL,
|
||||
zitadelURL: config.ZitadelURL,
|
||||
authRepo: authRepo,
|
||||
}
|
||||
statikFS, err := fs.NewWithNamespace("login")
|
||||
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start listener")
|
||||
|
||||
login.router = CreateRouter(login, statikFS)
|
||||
login.renderer = CreateRenderer(statikFS, config.LanguageCookieName, config.DefaultLanguage)
|
||||
login.parser = form.NewParser()
|
||||
login.Listen(ctx)
|
||||
}
|
||||
|
||||
func (l *Login) Listen(ctx context.Context) {
|
||||
if l.endpoint == "" {
|
||||
l.endpoint = ":80"
|
||||
} else {
|
||||
l.endpoint = ":" + l.endpoint
|
||||
}
|
||||
|
||||
defer logging.LogWithFields("APP-xUZof", "port", l.endpoint).Info("html is listening")
|
||||
httpListener, err := net.Listen("tcp", l.endpoint)
|
||||
logging.Log("CONFI-W5q2O").OnError(err).Panic("unable to start listener")
|
||||
|
||||
httpServer := &http.Server{
|
||||
Handler: l.router,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if err = httpServer.Shutdown(ctx); err != nil {
|
||||
logging.Log("APP-mJKTv").WithError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := httpServer.Serve(httpListener)
|
||||
logging.Log("APP-oSklt").OnError(err).Panic("unable to start listener")
|
||||
}()
|
||||
}
|
||||
|
||||
func setContext(ctx context.Context, resourceOwner string) context.Context {
|
||||
data := auth.CtxData{
|
||||
UserID: login,
|
||||
OrgID: resourceOwner,
|
||||
}
|
||||
return auth.SetCtxData(ctx, data)
|
||||
}
|
||||
63
internal/login/handler/login_handler.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplLogin = "login"
|
||||
)
|
||||
|
||||
type loginData struct {
|
||||
UserName string `schema:"username"`
|
||||
}
|
||||
|
||||
func (l *Login) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
authReq, err := l.getAuthRequest(r)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
if authReq == nil {
|
||||
http.Redirect(w, r, l.zitadelURL, http.StatusFound)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authReq)
|
||||
}
|
||||
|
||||
func (l *Login) handleUsername(w http.ResponseWriter, r *http.Request) {
|
||||
authSession, err := l.getAuthRequest(r)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authSession, err)
|
||||
return
|
||||
}
|
||||
l.renderLogin(w, r, authSession, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleUsernameCheck(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(loginData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
err = l.authRepo.CheckUsername(r.Context(), authReq.ID, data.UserName)
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authReq)
|
||||
}
|
||||
|
||||
func (l *Login) renderLogin(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "Login", errType, errMessage),
|
||||
UserName: authReq.UserName,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplLogin], data, nil)
|
||||
}
|
||||
20
internal/login/handler/logout_handler.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplLogoutDone = "logoutdone"
|
||||
)
|
||||
|
||||
func (l *Login) handleLogoutDone(w http.ResponseWriter, r *http.Request) {
|
||||
l.renderLogoutDone(w, r)
|
||||
}
|
||||
|
||||
func (l *Login) renderLogoutDone(w http.ResponseWriter, r *http.Request) {
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, nil, "Logout Done", "", ""),
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplLogoutDone], data, nil)
|
||||
}
|
||||
90
internal/login/handler/mail_verify_handler.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
queryCode = "code"
|
||||
queryUserID = "userID"
|
||||
|
||||
tmplMailVerification = "mail_verification"
|
||||
tmplMailVerified = "mail_verified"
|
||||
)
|
||||
|
||||
type mailVerificationFormData struct {
|
||||
Code string `schema:"code"`
|
||||
UserID string `schema:"userID"`
|
||||
Resend bool `schema:"resend"`
|
||||
}
|
||||
|
||||
type mailVerificationData struct {
|
||||
baseData
|
||||
UserID string
|
||||
}
|
||||
|
||||
func (l *Login) handleMailVerification(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.FormValue(queryUserID)
|
||||
code := r.FormValue(queryCode)
|
||||
if code != "" {
|
||||
l.checkMailCode(w, r, nil, userID, code)
|
||||
return
|
||||
}
|
||||
l.renderMailVerification(w, r, nil, userID, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(mailVerificationFormData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
if !data.Resend {
|
||||
l.checkMailCode(w, r, authReq, data.UserID, data.Code)
|
||||
return
|
||||
}
|
||||
userOrg := login
|
||||
if authReq != nil {
|
||||
userOrg = authReq.UserOrgID
|
||||
}
|
||||
err = l.authRepo.ResendEmailVerificationMail(setContext(r.Context(), userOrg), data.UserID)
|
||||
l.renderMailVerification(w, r, authReq, data.UserID, err)
|
||||
}
|
||||
|
||||
func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID, code string) {
|
||||
userOrg := login
|
||||
if authReq != nil {
|
||||
userID = authReq.UserID
|
||||
userOrg = authReq.UserOrgID
|
||||
}
|
||||
err := l.authRepo.VerifyEmail(setContext(r.Context(), userOrg), userID, code)
|
||||
if err != nil {
|
||||
l.renderMailVerification(w, r, authReq, userID, err)
|
||||
return
|
||||
}
|
||||
l.renderMailVerified(w, r, authReq)
|
||||
}
|
||||
|
||||
func (l *Login) renderMailVerification(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, userID string, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
if userID == "" {
|
||||
userID = authReq.UserID
|
||||
}
|
||||
data := mailVerificationData{
|
||||
baseData: l.getBaseData(r, authReq, "Mail Verification", errType, errMessage),
|
||||
UserID: userID,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMailVerification], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) renderMailVerified(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||
data := mailVerificationData{
|
||||
baseData: l.getBaseData(r, authReq, "Mail Verified", "", ""),
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMailVerified], data, nil)
|
||||
}
|
||||
20
internal/login/handler/mfa_init_done_handler.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplMfaInitDone = "mfainitdone"
|
||||
)
|
||||
|
||||
type mfaInitDoneData struct {
|
||||
}
|
||||
|
||||
func (l *Login) renderMfaInitDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaDoneData) {
|
||||
var errType, errMessage string
|
||||
data.baseData = l.getBaseData(r, authReq, "Mfa Init Done", errType, errMessage)
|
||||
data.UserName = authReq.UserName
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaInitDone], data, nil)
|
||||
}
|
||||
95
internal/login/handler/mfa_init_verify_handler.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/aaronarduino/goqrsvg"
|
||||
svg "github.com/ajstarks/svgo"
|
||||
"github.com/boombuler/barcode/qr"
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplMfaInitVerify = "mfainitverify"
|
||||
)
|
||||
|
||||
type mfaInitVerifyData struct {
|
||||
MfaType model.MfaType `schema:"mfaType"`
|
||||
Code string `schema:"code"`
|
||||
URL string `schema:"url"`
|
||||
Secret string `schema:"secret"`
|
||||
}
|
||||
|
||||
func (l *Login) handleMfaInitVerify(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(mfaInitVerifyData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
var verifyData *mfaVerifyData
|
||||
switch data.MfaType {
|
||||
case model.MfaTypeOTP:
|
||||
verifyData = l.handleOtpVerify(w, r, authReq, data)
|
||||
}
|
||||
|
||||
if verifyData != nil {
|
||||
l.renderMfaInitVerify(w, r, authReq, verifyData, err)
|
||||
return
|
||||
}
|
||||
|
||||
done := &mfaDoneData{
|
||||
MfaType: data.MfaType,
|
||||
}
|
||||
l.renderMfaInitDone(w, r, authReq, done)
|
||||
}
|
||||
|
||||
func (l *Login) handleOtpVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaInitVerifyData) *mfaVerifyData {
|
||||
err := l.authRepo.VerifyMfaOTPSetup(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, data.Code)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
mfadata := &mfaVerifyData{
|
||||
MfaType: data.MfaType,
|
||||
otpData: otpData{
|
||||
Secret: data.Secret,
|
||||
Url: data.URL,
|
||||
},
|
||||
}
|
||||
|
||||
return mfadata
|
||||
}
|
||||
|
||||
func (l *Login) renderMfaInitVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
data.baseData = l.getBaseData(r, authReq, "Mfa Init Verify", errType, errMessage)
|
||||
data.UserName = authReq.UserName
|
||||
if data.MfaType == model.MfaTypeOTP {
|
||||
code, err := generateQrCode(data.otpData.Url)
|
||||
if err == nil {
|
||||
data.otpData.QrCode = code
|
||||
}
|
||||
}
|
||||
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaInitVerify], data, nil)
|
||||
}
|
||||
|
||||
func generateQrCode(url string) (string, error) {
|
||||
var b bytes.Buffer
|
||||
s := svg.New(&b)
|
||||
|
||||
qrCode, err := qr.Encode(url, qr.M, qr.Auto)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
qs := goqrsvg.NewQrSVG(qrCode, 5)
|
||||
qs.StartQrSVG(s)
|
||||
qs.WriteQrSVG(s)
|
||||
|
||||
s.End()
|
||||
return string(b.Bytes()), nil
|
||||
}
|
||||
88
internal/login/handler/mfa_prompt_handler.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplMfaPrompt = "mfaprompt"
|
||||
)
|
||||
|
||||
type mfaPromptData struct {
|
||||
MfaProvider model.MfaType `schema:"provider"`
|
||||
Skip bool `schema:"skip"`
|
||||
}
|
||||
|
||||
func (l *Login) handleMfaPrompt(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(mfaPromptData)
|
||||
authSession, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authSession, err)
|
||||
return
|
||||
}
|
||||
if !data.Skip {
|
||||
mfaVerifyData := new(mfaVerifyData)
|
||||
mfaVerifyData.MfaType = data.MfaProvider
|
||||
l.handleMfaCreation(w, r, authSession, mfaVerifyData)
|
||||
return
|
||||
}
|
||||
err = l.authRepo.SkipMfaInit(setContext(r.Context(), authSession.UserOrgID), authSession.UserID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authSession, err)
|
||||
return
|
||||
}
|
||||
l.handleLogin(w, r)
|
||||
}
|
||||
|
||||
func (l *Login) renderMfaPrompt(w http.ResponseWriter, r *http.Request, authSession *model.AuthRequest, mfaPromptData *model.MfaPromptStep, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
data := mfaData{
|
||||
baseData: l.getBaseData(r, authSession, "Mfa Prompt", errType, errMessage),
|
||||
UserName: authSession.UserName,
|
||||
}
|
||||
|
||||
if mfaPromptData == nil {
|
||||
l.renderError(w, r, authSession, caos_errs.ThrowPreconditionFailed(nil, "APP-XU0tj", "No available mfa providers"))
|
||||
return
|
||||
}
|
||||
|
||||
data.MfaProviders = mfaPromptData.MfaProviders
|
||||
data.MfaRequired = mfaPromptData.Required
|
||||
|
||||
if len(mfaPromptData.MfaProviders) == 1 && mfaPromptData.Required {
|
||||
data := &mfaVerifyData{
|
||||
MfaType: mfaPromptData.MfaProviders[0],
|
||||
}
|
||||
l.handleMfaCreation(w, r, authSession, data)
|
||||
return
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaPrompt], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleMfaCreation(w http.ResponseWriter, r *http.Request, authSession *model.AuthRequest, data *mfaVerifyData) {
|
||||
switch data.MfaType {
|
||||
case model.MfaTypeOTP:
|
||||
l.handleOtpCreation(w, r, authSession, data)
|
||||
return
|
||||
}
|
||||
l.renderError(w, r, authSession, caos_errs.ThrowPreconditionFailed(nil, "APP-Or3HO", "No available mfa providers"))
|
||||
}
|
||||
|
||||
func (l *Login) handleOtpCreation(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, data *mfaVerifyData) {
|
||||
otp, err := l.authRepo.AddMfaOTP(setContext(r.Context(), authReq.UserOrgID), authReq.UserID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
|
||||
data.otpData = otpData{
|
||||
Secret: otp.SecretString,
|
||||
Url: otp.Url,
|
||||
}
|
||||
l.renderMfaInitVerify(w, r, authReq, data, nil)
|
||||
}
|
||||
49
internal/login/handler/mfa_verify_handler.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplMfaVerify = "mfaverify"
|
||||
)
|
||||
|
||||
type mfaVerifyFormData struct {
|
||||
MfaType model.MfaType `schema:"mfaType"`
|
||||
Code string `schema:"code"`
|
||||
}
|
||||
|
||||
func (l *Login) handleMfaVerify(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(mfaVerifyFormData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
if data.MfaType == model.MfaTypeOTP {
|
||||
err = l.authRepo.VerifyMfaOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Code, model.BrowserInfoFromRequest(r))
|
||||
}
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authReq)
|
||||
}
|
||||
|
||||
func (l *Login) renderMfaVerify(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, verificationStep *model.MfaVerificationStep, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "Mfa Verify", errType, errMessage),
|
||||
UserName: authReq.UserName,
|
||||
}
|
||||
if verificationStep != nil {
|
||||
data.MfaProviders = verificationStep.MfaProviders
|
||||
data.SelectedMfaProvider = verificationStep.MfaProviders[0]
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplMfaVerify], data, nil)
|
||||
}
|
||||
42
internal/login/handler/password_handler.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplPassword = "password"
|
||||
)
|
||||
|
||||
type passwordData struct {
|
||||
Password string `schema:"password"`
|
||||
}
|
||||
|
||||
func (l *Login) renderPassword(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "Password", errType, errMessage),
|
||||
UserName: authReq.UserName,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplPassword], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handlePasswordCheck(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(passwordData)
|
||||
authReq, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, data.Password, model.BrowserInfoFromRequest(r))
|
||||
if err != nil {
|
||||
l.renderPassword(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authReq)
|
||||
}
|
||||
32
internal/login/handler/password_reset_handler.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplPasswordResetDone = "passwordresetdone"
|
||||
)
|
||||
|
||||
func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
|
||||
authReq, err := l.getAuthRequest(r)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
err = l.authRepo.RequestPasswordReset(setContext(r.Context(), authReq.UserOrgID), authReq.UserName)
|
||||
l.renderPasswordResetDone(w, r, authReq, err)
|
||||
}
|
||||
|
||||
func (l *Login) renderPasswordResetDone(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
data := userData{
|
||||
baseData: l.getBaseData(r, authReq, "Password Reset Done", errType, errMessage),
|
||||
UserName: authReq.UserName,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplPasswordResetDone], data, nil)
|
||||
}
|
||||
119
internal/login/handler/register_handler.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"golang.org/x/text/language"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplRegister = "register"
|
||||
|
||||
globalRO = "GlobalResourceOwner"
|
||||
)
|
||||
|
||||
type registerFormData struct {
|
||||
Email string `schema:"email"`
|
||||
Firstname string `schema:"firstname"`
|
||||
Lastname string `schema:"lastname"`
|
||||
Language string `schema:"language"`
|
||||
Gender int32 `schema:"gender"`
|
||||
Password string `schema:"password"`
|
||||
Password2 string `schema:"password2"`
|
||||
}
|
||||
|
||||
type registerData struct {
|
||||
baseData
|
||||
registerFormData
|
||||
}
|
||||
|
||||
func (l *Login) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(registerFormData)
|
||||
authRequest, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authRequest, err)
|
||||
return
|
||||
}
|
||||
l.renderRegister(w, r, authRequest, data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(registerFormData)
|
||||
authRequest, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authRequest, err)
|
||||
return
|
||||
}
|
||||
if data.Password != data.Password2 {
|
||||
err := caos_errs.ThrowInvalidArgument(nil, "VIEW-KaGue", "passwords dont match")
|
||||
l.renderRegister(w, r, authRequest, data, err)
|
||||
return
|
||||
}
|
||||
iam, err := l.authRepo.GetIam(r.Context())
|
||||
if err != nil {
|
||||
l.renderRegister(w, r, authRequest, data, err)
|
||||
return
|
||||
}
|
||||
user, err := l.authRepo.Register(setContext(r.Context(), iam.GlobalOrgID), data.toUserModel(), iam.GlobalOrgID)
|
||||
if err != nil {
|
||||
l.renderRegister(w, r, authRequest, data, err)
|
||||
return
|
||||
}
|
||||
if authRequest == nil {
|
||||
http.Redirect(w, r, l.zitadelURL, http.StatusFound)
|
||||
return
|
||||
}
|
||||
authRequest.UserName = user.UserName
|
||||
l.renderNextStep(w, r, authRequest)
|
||||
}
|
||||
|
||||
func (l *Login) renderRegister(w http.ResponseWriter, r *http.Request, authRequest *model.AuthRequest, formData *registerFormData, err error) {
|
||||
var errType, errMessage string
|
||||
if err != nil {
|
||||
errMessage = err.Error()
|
||||
}
|
||||
if formData == nil {
|
||||
formData = new(registerFormData)
|
||||
}
|
||||
if formData.Language == "" {
|
||||
formData.Language = l.renderer.Lang(r).String()
|
||||
}
|
||||
data := registerData{
|
||||
baseData: l.getBaseData(r, authRequest, "Register", errType, errMessage),
|
||||
registerFormData: *formData,
|
||||
}
|
||||
funcs := map[string]interface{}{
|
||||
"selectedLanguage": func(l string) bool {
|
||||
if formData == nil {
|
||||
return false
|
||||
}
|
||||
return formData.Language == l
|
||||
},
|
||||
"selectedGender": func(g int32) bool {
|
||||
if formData == nil {
|
||||
return false
|
||||
}
|
||||
return formData.Gender == g
|
||||
},
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplRegister], data, funcs)
|
||||
}
|
||||
|
||||
func (d registerFormData) toUserModel() *usr_model.User {
|
||||
return &usr_model.User{
|
||||
Profile: &usr_model.Profile{
|
||||
FirstName: d.Firstname,
|
||||
LastName: d.Lastname,
|
||||
PreferredLanguage: language.Make(d.Language),
|
||||
Gender: usr_model.Gender(d.Gender),
|
||||
},
|
||||
Password: &usr_model.Password{
|
||||
SecretString: d.Password,
|
||||
},
|
||||
Email: &usr_model.Email{
|
||||
EmailAddress: d.Email,
|
||||
},
|
||||
}
|
||||
}
|
||||
260
internal/login/handler/renderer.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"github.com/caos/zitadel/internal/renderer"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplError = "error"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
*renderer.Renderer
|
||||
}
|
||||
|
||||
func CreateRenderer(staticDir http.FileSystem, cookieName string, defaultLanguage language.Tag) *Renderer {
|
||||
r := new(Renderer)
|
||||
tmplMapping := map[string]string{
|
||||
tmplError: "error.html",
|
||||
tmplLogin: "login.html",
|
||||
tmplUserSelection: "select_user.html",
|
||||
tmplPassword: "password.html",
|
||||
tmplMfaVerify: "mfa_verify.html",
|
||||
tmplMfaPrompt: "mfa_prompt.html",
|
||||
tmplMfaInitVerify: "mfa_init_verify.html",
|
||||
tmplMfaInitDone: "mfa_init_done.html",
|
||||
tmplMailVerification: "mail_verification.html",
|
||||
tmplMailVerified: "mail_verified.html",
|
||||
tmplInitPassword: "init_password.html",
|
||||
tmplInitPasswordDone: "init_password_done.html",
|
||||
tmplInitUser: "init_user.html",
|
||||
tmplInitUserDone: "init_user_done.html",
|
||||
tmplPasswordResetDone: "password_reset_done.html",
|
||||
tmplChangePassword: "change_password.html",
|
||||
tmplChangePasswordDone: "change_password_done.html",
|
||||
tmplRegister: "register.html",
|
||||
tmplLogoutDone: "logout_done.html",
|
||||
}
|
||||
funcs := map[string]interface{}{
|
||||
"resourceUrl": func(file string) string {
|
||||
return path.Join(EndpointResources, file)
|
||||
},
|
||||
"resourceThemeUrl": func(file, theme string) string {
|
||||
return path.Join(EndpointResources, "themes", theme, file)
|
||||
},
|
||||
"loginUrl": func() string {
|
||||
return EndpointLogin
|
||||
},
|
||||
"registerUrl": func(id string) string {
|
||||
return fmt.Sprintf("%s?%s=%s", EndpointRegister, queryAuthRequestID, id)
|
||||
},
|
||||
"usernameUrl": func() string {
|
||||
return EndpointUsername
|
||||
},
|
||||
"usernameChangeUrl": func(id string) string {
|
||||
return fmt.Sprintf("%s?%s=%s", EndpointUsername, queryAuthRequestID, id)
|
||||
},
|
||||
"userSelectionUrl": func() string {
|
||||
return EndpointUserSelection
|
||||
},
|
||||
"passwordResetUrl": func(id string) string {
|
||||
return fmt.Sprintf("%s?%s=%s", EndpointPasswordReset, queryAuthRequestID, id)
|
||||
},
|
||||
"passwordUrl": func() string {
|
||||
return EndpointPassword
|
||||
},
|
||||
"mfaVerifyUrl": func() string {
|
||||
return EndpointMfaVerify
|
||||
},
|
||||
"mfaPromptUrl": func() string {
|
||||
return EndpointMfaPrompt
|
||||
},
|
||||
"mfaInitVerifyUrl": func() string {
|
||||
return EndpointMfaInitVerify
|
||||
},
|
||||
"mailVerificationUrl": func() string {
|
||||
return EndpointMailVerification
|
||||
},
|
||||
"initPasswordUrl": func() string {
|
||||
return EndpointInitPassword
|
||||
},
|
||||
"initUserUrl": func() string {
|
||||
return EndpointInitUser
|
||||
},
|
||||
"changePasswordUrl": func() string {
|
||||
return EndpointChangePassword
|
||||
},
|
||||
"registrationUrl": func() string {
|
||||
return EndpointRegister
|
||||
},
|
||||
"selectedLanguage": func(l string) bool {
|
||||
return false
|
||||
},
|
||||
"selectedGender": func(g int32) bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
var err error
|
||||
r.Renderer, err = renderer.NewRenderer(
|
||||
staticDir,
|
||||
tmplMapping, funcs,
|
||||
i18n.TranslatorConfig{DefaultLanguage: defaultLanguage, CookieName: cookieName},
|
||||
)
|
||||
logging.Log("APP-40tSoJ").OnError(err).WithError(err).Panic("error creating renderer")
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest) {
|
||||
authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID)
|
||||
if err != nil {
|
||||
l.renderInternalError(w, r, authReq, errors.ThrowInternal(nil, "APP-sio0W", "could not get authreq"))
|
||||
}
|
||||
if len(authReq.PossibleSteps) == 0 {
|
||||
l.renderInternalError(w, r, authReq, errors.ThrowInternal(nil, "APP-9sdp4", "no possible steps"))
|
||||
return
|
||||
}
|
||||
l.chooseNextStep(w, r, authReq, 0, nil)
|
||||
}
|
||||
|
||||
func (l *Login) renderError(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||
if authReq == nil || len(authReq.PossibleSteps) == 0 {
|
||||
l.renderInternalError(w, r, authReq, errors.ThrowInternal(err, "APP-OVOiT", "no possible steps"))
|
||||
return
|
||||
}
|
||||
l.chooseNextStep(w, r, authReq, 0, err)
|
||||
}
|
||||
|
||||
func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, stepNumber int, err error) {
|
||||
switch step := authReq.PossibleSteps[stepNumber].(type) {
|
||||
case *model.LoginStep:
|
||||
if len(authReq.PossibleSteps) > 1 {
|
||||
l.chooseNextStep(w, r, authReq, 1, err)
|
||||
return
|
||||
}
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
case *model.SelectUserStep:
|
||||
l.renderUserSelection(w, r, authReq, step)
|
||||
case *model.InitPasswordStep:
|
||||
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
||||
case *model.PasswordStep:
|
||||
l.renderPassword(w, r, authReq, nil)
|
||||
case *model.MfaVerificationStep:
|
||||
l.renderMfaVerify(w, r, authReq, step, err)
|
||||
case *model.RedirectToCallbackStep:
|
||||
if len(authReq.PossibleSteps) > 1 {
|
||||
l.chooseNextStep(w, r, authReq, 1, err)
|
||||
return
|
||||
}
|
||||
l.redirectToCallback(w, r, authReq)
|
||||
case *model.ChangePasswordStep:
|
||||
l.renderChangePassword(w, r, authReq, err)
|
||||
case *model.VerifyEMailStep:
|
||||
l.renderMailVerification(w, r, authReq, "", err)
|
||||
case *model.MfaPromptStep:
|
||||
l.renderMfaPrompt(w, r, authReq, step, err)
|
||||
case *model.InitUserStep:
|
||||
l.renderInitUser(w, r, authReq, "", "", nil)
|
||||
default:
|
||||
l.renderInternalError(w, r, authReq, errors.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
|
||||
var msg string
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
}
|
||||
data := l.getBaseData(r, authReq, "Error", "Internal", msg)
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplError], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title string, errType, errMessage string) baseData {
|
||||
return baseData{
|
||||
errorData: errorData{
|
||||
ErrType: errType,
|
||||
ErrMessage: errMessage,
|
||||
},
|
||||
Lang: l.renderer.Lang(r).String(),
|
||||
Title: title,
|
||||
Theme: l.getTheme(r),
|
||||
ThemeMode: l.getThemeMode(r),
|
||||
AuthReqID: getRequestID(authReq, r),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Login) getTheme(r *http.Request) string {
|
||||
return "zitadel" //TODO: impl
|
||||
}
|
||||
|
||||
func (l *Login) getThemeMode(r *http.Request) string {
|
||||
return "" //TODO: impl
|
||||
}
|
||||
|
||||
func getRequestID(authReq *model.AuthRequest, r *http.Request) string {
|
||||
if authReq != nil {
|
||||
return authReq.ID
|
||||
}
|
||||
return r.FormValue(queryAuthRequestID)
|
||||
}
|
||||
|
||||
type baseData struct {
|
||||
errorData
|
||||
Lang string
|
||||
Title string
|
||||
Theme string
|
||||
ThemeMode string
|
||||
AuthReqID string
|
||||
}
|
||||
|
||||
type errorData struct {
|
||||
ErrType string
|
||||
ErrMessage string
|
||||
}
|
||||
|
||||
type userData struct {
|
||||
baseData
|
||||
UserName string
|
||||
PasswordChecked string
|
||||
MfaProviders []model.MfaType
|
||||
SelectedMfaProvider model.MfaType
|
||||
}
|
||||
|
||||
type userSelectionData struct {
|
||||
baseData
|
||||
Users []model.UserSelection
|
||||
}
|
||||
|
||||
type mfaData struct {
|
||||
baseData
|
||||
UserName string
|
||||
MfaProviders []model.MfaType
|
||||
MfaRequired bool
|
||||
}
|
||||
|
||||
type mfaVerifyData struct {
|
||||
baseData
|
||||
UserName string
|
||||
MfaType model.MfaType
|
||||
otpData
|
||||
}
|
||||
|
||||
type mfaDoneData struct {
|
||||
baseData
|
||||
UserName string
|
||||
MfaType model.MfaType
|
||||
}
|
||||
|
||||
type otpData struct {
|
||||
Url string
|
||||
Secret string
|
||||
QrCode string
|
||||
}
|
||||
9
internal/login/handler/resources_handler.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (l *Login) handleResources(staticDir http.FileSystem) http.Handler {
|
||||
return http.FileServer(staticDir)
|
||||
}
|
||||
58
internal/login/handler/router.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
EndpointRoot = "/"
|
||||
EndpointHealthz = "/healthz"
|
||||
EndpointReadiness = "/ready"
|
||||
EndpointLogin = "/login"
|
||||
EndpointUsername = "/username"
|
||||
EndpointUserSelection = "/userselection"
|
||||
EndpointPassword = "/password"
|
||||
EndpointInitPassword = "/password/init"
|
||||
EndpointChangePassword = "/password/change"
|
||||
EndpointPasswordReset = "/password/reset"
|
||||
EndpointInitUser = "/user/init"
|
||||
EndpointMfaVerify = "/mfa/verify"
|
||||
EndpointMfaPrompt = "/mfa/prompt"
|
||||
EndpointMfaInitVerify = "/mfa/init/verify"
|
||||
EndpointMailVerification = "/mail/verification"
|
||||
EndpointMailVerified = "/mail/verified"
|
||||
EndpointRegister = "/register"
|
||||
EndpointLogoutDone = "/logout/done"
|
||||
|
||||
EndpointResources = "/resources"
|
||||
)
|
||||
|
||||
func CreateRouter(login *Login, staticDir http.FileSystem) *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc(EndpointRoot, login.handleLogin).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointHealthz, login.handleHealthz).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointReadiness, login.handleReadiness).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc(EndpointUsername, login.handleUsername).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointUsername, login.handleUsernameCheck).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointUserSelection, login.handleSelectUser).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointPassword, login.handlePasswordCheck).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointInitPassword, login.handleInitPassword).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointInitPassword, login.handleInitPasswordCheck).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointPasswordReset, login.handlePasswordReset).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointInitUser, login.handleInitUser).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointInitUser, login.handleInitUserCheck).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointMfaVerify, login.handleMfaVerify).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointMfaPrompt, login.handleMfaPrompt).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointMfaInitVerify, login.handleMfaInitVerify).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointMailVerification, login.handleMailVerificationCheck).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointChangePassword, login.handleChangePassword).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointRegister, login.handleRegister).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointRegister, login.handleRegisterCheck).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointLogoutDone, login.handleLogoutDone).Methods(http.MethodGet)
|
||||
router.PathPrefix(EndpointResources).Handler(login.handleResources(staticDir)).Methods(http.MethodGet)
|
||||
return router
|
||||
}
|
||||
42
internal/login/handler/select_user_handler.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
tmplUserSelection = "userselection"
|
||||
)
|
||||
|
||||
type userSelectionFormData struct {
|
||||
UserID string `schema:"userID"`
|
||||
}
|
||||
|
||||
func (l *Login) renderUserSelection(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, selectionData *model.SelectUserStep) {
|
||||
var errType, errMessage string
|
||||
data := userSelectionData{
|
||||
baseData: l.getBaseData(r, authReq, "Select User", errType, errMessage),
|
||||
Users: selectionData.Users,
|
||||
}
|
||||
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplUserSelection], data, nil)
|
||||
}
|
||||
|
||||
func (l *Login) handleSelectUser(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(userSelectionFormData)
|
||||
authSession, err := l.getAuthRequestAndParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authSession, err)
|
||||
return
|
||||
}
|
||||
if data.UserID == "0" {
|
||||
l.renderLogin(w, r, authSession, nil)
|
||||
return
|
||||
}
|
||||
err = l.authRepo.SelectUser(r.Context(), authSession.ID, data.UserID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authSession, err)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authSession)
|
||||
}
|
||||
17
internal/login/login.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/login/handler"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Handler handler.Config
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config Config, systemDefaults sd.SystemDefaults, authRepo *eventsourcing.EsRepository) {
|
||||
handler.StartLogin(ctx, config.Handler, authRepo)
|
||||
}
|
||||
119
internal/login/static/i18n/de.yaml
Normal file
@@ -0,0 +1,119 @@
|
||||
Password:
|
||||
Title: Passwort
|
||||
Description: Gib deine Benutzerdaten ein.
|
||||
Password: Passwort
|
||||
|
||||
Login:
|
||||
Title: Anmeldung
|
||||
Description: Gib deine Benutzerdaten ein.
|
||||
Username: Benutzername
|
||||
|
||||
UserSelection:
|
||||
Title: Account auswählen
|
||||
Description: Wähle deinen Account aus.
|
||||
OtherUser: Anderer Benutzer
|
||||
SessionState0: aktiv
|
||||
SessionState1: inaktiv
|
||||
|
||||
MfaVerify:
|
||||
Title: Multifaktor verifizieren
|
||||
Description: Verifiziere deinen Multifaktor
|
||||
OTP: OTP
|
||||
Code: Code
|
||||
|
||||
InitPassword:
|
||||
Title: Passwort setzen
|
||||
Description: Du hast einen Code erhalten, welcher im untenstehenden Formular eingegeben werden muss um ein neues Passwort zu setzen.
|
||||
Code: Code
|
||||
NewPassword: Neues Passwort
|
||||
NewPasswordConfirm: Passwort bestätigen
|
||||
|
||||
InitPasswordDone:
|
||||
Title: Passwort gesetzt
|
||||
Description: Passwort erfolgreich gesetzt
|
||||
|
||||
InitUser:
|
||||
Title: User aktivieren
|
||||
Description: Du hast einen Code erhalten, welcher im untenstehenden Formular eingegeben werden muss um deine EMail zu verifizieren und ein neues Passwort zu setzen.
|
||||
Code: Code
|
||||
NewPassword: Neues Passwort
|
||||
NewPasswordConfirm: Passwort bestätigen
|
||||
|
||||
InitUserDone:
|
||||
Title: User aktiviert
|
||||
Description: EMail verifiziert und Passwort erfolgreich gesetzt
|
||||
|
||||
MfaPrompt:
|
||||
Title: Multifaktor hinzufügen
|
||||
Description: Möchtest du einen Mulitfaktor hinzufügen?
|
||||
Provider0: OTP
|
||||
Provider1: SMS
|
||||
|
||||
MfaInitVerify:
|
||||
Title: Multifaktor Verifizierung
|
||||
Description: Verifiziere deinen Multifaktor
|
||||
OtpDescription: Scanne den Code mit einem Authentifizierungs-App (z.B Google Authentificator) oder kopiere das Secret und gib anschliessend den Code ein.
|
||||
Secret: Secret
|
||||
Code: Code
|
||||
|
||||
MfaInitDone:
|
||||
Title: Multifaktor Verifizierung erstellt
|
||||
Description: Multifikator Verifizierung erfolgreich abgeschlossen. Der Multifaktor muss bei jeder Anmeldung eingegeben werden, dies beinhaltet auch den aktuellen Authentifizierungs Prozess.
|
||||
|
||||
PasswordChange:
|
||||
Title: Passwort ändern
|
||||
Description: Ändere dein Password in dem du dein altes und dann dein neuen Passwort eingibst.
|
||||
OldPassword: Altes Passwort
|
||||
NewPassword: Neues Passwort
|
||||
|
||||
PasswordChangeDone:
|
||||
Title: Passwort ändern
|
||||
Description: Das Passwort wurde erfolgreich geändert.
|
||||
|
||||
PasswordResetDone:
|
||||
Title: Resetlink versendet
|
||||
Description: Prüfe dein E-Mail Postfach, um ein neues Passwort zu setzen.
|
||||
|
||||
PasswordSetDone:
|
||||
Title: Passwort gesetzt
|
||||
Description: Das Passwort wurde erfolgreich gesetzt.
|
||||
|
||||
EmailVerification:
|
||||
Title: E-Mail Verifizierung
|
||||
Description: Du hast ein E-Mail zur Verifizierung deiner E-Mail Adresse bekommen. Gib den Code im untenstehenden Formular ein. Mit erneut versenden, wird dir ein neues E-Mail zugestellt.
|
||||
Code: Code
|
||||
|
||||
EmailVerificationDone:
|
||||
Title: E-Mail Verifizierung
|
||||
Description: Deine E-Mail Adresse wurde erfolgreich verifiziert.
|
||||
|
||||
Registration:
|
||||
Title: Registration
|
||||
Description: Gib deine Benutzerangaben an. Die E-Mail Adresse wird als Benutzernamen verwendet.
|
||||
Email: E-Mail
|
||||
Firstname: Vorname
|
||||
Lastname: Nachname
|
||||
Language: Sprache
|
||||
German: Deutsch
|
||||
English: English
|
||||
Gender: Geschlecht
|
||||
Female: weiblich
|
||||
Male: männlich
|
||||
Diverse: diverse
|
||||
Password: Passwort
|
||||
Password2: Passwort wiederholen
|
||||
|
||||
LogoutDone:
|
||||
Title: Ausgeloggt
|
||||
Description: Du wurdest erfolgreich ausgeloggt.
|
||||
|
||||
Actions:
|
||||
Login: anmelden
|
||||
Next: weiter
|
||||
Back: zurück
|
||||
Resend: erneut senden
|
||||
Skip: überspringen
|
||||
Register: registrieren
|
||||
ForgotPassword: Password zurücksetzen
|
||||
|
||||
optional: (optional)
|
||||
119
internal/login/static/i18n/en.yaml
Normal file
@@ -0,0 +1,119 @@
|
||||
Login:
|
||||
Title: Login
|
||||
Description: Enter your logindata.
|
||||
Username: Username
|
||||
|
||||
UserSelection:
|
||||
Title: Select account
|
||||
Description: Select your account.
|
||||
OtherUser: Other User
|
||||
SessionState0: active
|
||||
SessionState1: inactive
|
||||
|
||||
Password:
|
||||
Title: Password
|
||||
Description: Enter your logindata.
|
||||
Password: Password
|
||||
|
||||
MfaVerify:
|
||||
Title: Verify Multificator
|
||||
Description: Verify your multifactor
|
||||
OTP: OTP
|
||||
Code: Code
|
||||
|
||||
InitPassword:
|
||||
Title: Set Password
|
||||
Description: You have received a code, which you have to enter in the form below, to set your new password.
|
||||
Code: Code
|
||||
NewPassword: New Password
|
||||
NewPasswordConfirm: Confirm Password
|
||||
|
||||
InitPasswordDone:
|
||||
Title: Passwortd set
|
||||
Description: Password successfully set
|
||||
|
||||
InitUser:
|
||||
Title: Activate User
|
||||
Description: You have received a code, which you have to enter in the form below, to verify your email and set your new password.
|
||||
Code: Code
|
||||
NewPassword: New Password
|
||||
NewPasswordConfirm: Confirm Password
|
||||
|
||||
InitUserDone:
|
||||
Title: User activated
|
||||
Description: Email verified and Password successfully set
|
||||
|
||||
MfaPrompt:
|
||||
Title: Multifactor Setup
|
||||
Description: Would you like to setup multifactor authentication?
|
||||
Provider0: OTP
|
||||
Provider1: SMS
|
||||
|
||||
MfaInitVerify:
|
||||
Title: Multifactor Verification
|
||||
Description: Verify your multifactor.
|
||||
OtpDescription: Scan the code with your authenticator app (e.g Google-Authenticator) or copy the secret and insert the generated code below.
|
||||
Secret: Secret
|
||||
Code: Code
|
||||
|
||||
MfaInitDone:
|
||||
Title: Multifcator Verification done
|
||||
Description: Multifactor verification successfully done. The multifactor has to be entered on each login, even in the actual authentification process.
|
||||
|
||||
PasswordChange:
|
||||
Title: Change Password
|
||||
Description: Change your password. Enter your old and new password.
|
||||
OldPassword: Old Password
|
||||
NewPassword: New Password
|
||||
|
||||
PasswordChangeDone:
|
||||
Title: Change Password
|
||||
Description: Your password was changed successfully.
|
||||
|
||||
PasswordResetDone:
|
||||
Title: Reset link set
|
||||
Description: Check your email to to reset your password.
|
||||
|
||||
PasswordSetDone:
|
||||
Title: Password set
|
||||
Description: Your password was set successfully.
|
||||
|
||||
EmailVerification:
|
||||
Title: E-Mail Verification
|
||||
Description: We have sent you an email to verify your address. Please enter the code in the form below.
|
||||
Code: Code
|
||||
|
||||
EmailVerificationDone:
|
||||
Title: E-Mail Verification
|
||||
Description: Your email address has been successfully verified.
|
||||
|
||||
Registration:
|
||||
Title: Registration
|
||||
Description: Enter your Userdata. Your email address will be used as username.
|
||||
Email: E-Mail
|
||||
Firstname: Firstname
|
||||
Lastname: Lastname
|
||||
Language: Language
|
||||
German: Deutsch
|
||||
English: English
|
||||
Gender: Gender
|
||||
Female: Female
|
||||
Male: Male
|
||||
Diverse: diverse / X
|
||||
Password: Password
|
||||
Password2: Password confirmation
|
||||
|
||||
LogoutDone:
|
||||
Title: Logged out
|
||||
Description: You have logged out successfully.
|
||||
|
||||
Actions:
|
||||
Login: login
|
||||
Next: next
|
||||
Back: back
|
||||
Resend: resend
|
||||
Skip: skip
|
||||
Register: register
|
||||
ForgotPassword: Reset password
|
||||
|
||||
optional: (optional)
|
||||
BIN
internal/login/static/resources/fonts/ailerons/ailerons.otf
Normal file
BIN
internal/login/static/resources/fonts/lato/Lato-Black.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-BlackItalic.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-Bold.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-BoldItalic.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-Italic.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-Light.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-LightItalic.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-Regular.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-Thin.ttf
Executable file
BIN
internal/login/static/resources/fonts/lato/Lato-ThinItalic.ttf
Executable file
93
internal/login/static/resources/fonts/lato/OFL.txt
Executable file
@@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
7
internal/login/static/resources/generate.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package resources
|
||||
|
||||
// scss
|
||||
//go:generate sass themes/scss/zitadel/dark.scss themes/zitadel/css/dark.css
|
||||
//go:generate sass themes/scss/zitadel/light.scss themes/zitadel/css/light.css
|
||||
//go:generate sass themes/scss/caos/dark.scss themes/caos/css/dark.css
|
||||
//go:generate sass themes/scss/caos/light.scss themes/caos/css/light.css
|
||||
257
internal/login/static/resources/themes/caos/css/dark.css
Normal file
@@ -0,0 +1,257 @@
|
||||
@font-face {
|
||||
font-family: Aileron;
|
||||
src: url(../../../fonts/ailerons/ailerons.otf) format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Thin.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-ThinItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Light.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-LightItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Regular.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Italic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Bold.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BoldItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Black.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BlackItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Material Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../../../fonts/material/MaterialIcons-Regular.eot);
|
||||
/* For IE6-8 */
|
||||
src: local("Material Icons"), local("MaterialIcons-Regular"), url(../../../fonts/material/MaterialIcons-Regular.woff2) format("woff2"), url(../../../fonts/material/MaterialIcons-Regular.woff) format("woff"), url(../../../fonts/material/MaterialIcons-Regular.ttf) format("truetype");
|
||||
}
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
font-family: Lato;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #282828;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
font-family: Aileron;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-width: 300;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 8px;
|
||||
}
|
||||
header .logo {
|
||||
background-image: url("../logo-dark.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 80px;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #760038;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
a:hover {
|
||||
color: #f60075;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
background-color: #282828;
|
||||
color: #760038;
|
||||
border: 2px solid #760038;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: 50px;
|
||||
transition: all 0.3s ease 0s;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #f60075;
|
||||
border: 2px solid #f60075;
|
||||
}
|
||||
button.primary {
|
||||
background-color: #760038;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
button.primary:hover {
|
||||
background-color: #f60075;
|
||||
}
|
||||
button > .sessionstate {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
input:not([type=radio]), select {
|
||||
background-color: #252525;
|
||||
color: white;
|
||||
height: 50px;
|
||||
border: 2px solid #595959;
|
||||
border-radius: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
form .field {
|
||||
display: grid;
|
||||
padding: 10px 0;
|
||||
}
|
||||
form .field.radio-button {
|
||||
display: flex;
|
||||
}
|
||||
form .field.radio-button input[type=radio] {
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
form .field.radio-button label {
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
padding: 3px 0 0 15px;
|
||||
width: 100%;
|
||||
}
|
||||
form label {
|
||||
color: #898989;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
form label span.optional {
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
}
|
||||
form .actions {
|
||||
padding: 20px 0;
|
||||
}
|
||||
form .actions .right {
|
||||
float: right;
|
||||
}
|
||||
form .actions button, form .actions a {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#copy-secret {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
text-align: center;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:white"] {
|
||||
fill: #282828 !important;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:black"] {
|
||||
fill: white !important;
|
||||
}
|
||||
|
||||
#secret .copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url("../gradientdeco-full.svg");
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
height: 44vw;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
/* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Support for IE. */
|
||||
font-feature-settings: "liga";
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=dark.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCGW;EDFX;EACA;;;AAGJ;EACI;;;AAGJ;EACI,kBCDc;EDEd,OCDQ;;;ADIZ;EACI,OCLQ;EDMR,aCZS;EDaT;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCnCW;EDoCX;EACA;EACA;;AAEA;EACI,OCxCY;;;AD4CpB;EACI;EACA,kBCjDc;EDkDd,OChDW;EDiDX;EACA;EACA;EACA;EACA,QE/DU;EFgEV;EACA;EACA;;AACA;EACI,kBCzDY;ED0DZ;;AAGJ;EACI,kBC/DO;EDgEP,OCjEI;EDkEJ;;AACA;EACI,kBClEQ;;ADsEhB;EACI;;;AAIR;EACI,kBE7EmB;EF8EnB,OC/EQ;EDgFR,QEzFU;EF0FV;EACA;EACA;;;AAIA;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI,OE9GK;EF+GL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKJ;EACI;EACA;;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA","file":"dark.css"}
|
||||
299
internal/login/static/resources/themes/caos/css/light.css
Normal file
@@ -0,0 +1,299 @@
|
||||
@font-face {
|
||||
font-family: Aileron;
|
||||
src: url(../../../fonts/ailerons/ailerons.otf) format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Thin.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-ThinItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Light.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-LightItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Regular.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Italic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Bold.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BoldItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Black.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BlackItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Material Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../../../fonts/material/MaterialIcons-Regular.eot);
|
||||
/* For IE6-8 */
|
||||
src: local("Material Icons"), local("MaterialIcons-Regular"), url(../../../fonts/material/MaterialIcons-Regular.woff2) format("woff2"), url(../../../fonts/material/MaterialIcons-Regular.woff) format("woff"), url(../../../fonts/material/MaterialIcons-Regular.ttf) format("truetype");
|
||||
}
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
font-family: Lato;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #282828;
|
||||
color: white;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
font-family: Aileron;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-width: 300;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 8px;
|
||||
}
|
||||
header .logo {
|
||||
background-image: url("../logo-dark.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 80px;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #760038;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
a:hover {
|
||||
color: #f60075;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
background-color: #282828;
|
||||
color: #760038;
|
||||
border: 2px solid #760038;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: 50px;
|
||||
transition: all 0.3s ease 0s;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #f60075;
|
||||
border: 2px solid #f60075;
|
||||
}
|
||||
button.primary {
|
||||
background-color: #760038;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
button.primary:hover {
|
||||
background-color: #f60075;
|
||||
}
|
||||
button > .sessionstate {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
input:not([type=radio]), select {
|
||||
background-color: #252525;
|
||||
color: white;
|
||||
height: 50px;
|
||||
border: 2px solid #595959;
|
||||
border-radius: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
form .field {
|
||||
display: grid;
|
||||
padding: 10px 0;
|
||||
}
|
||||
form .field.radio-button {
|
||||
display: flex;
|
||||
}
|
||||
form .field.radio-button input[type=radio] {
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
form .field.radio-button label {
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
padding: 3px 0 0 15px;
|
||||
width: 100%;
|
||||
}
|
||||
form label {
|
||||
color: #898989;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
form label span.optional {
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
}
|
||||
form .actions {
|
||||
padding: 20px 0;
|
||||
}
|
||||
form .actions .right {
|
||||
float: right;
|
||||
}
|
||||
form .actions button, form .actions a {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#copy-secret {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
text-align: center;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:white"] {
|
||||
fill: #282828 !important;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:black"] {
|
||||
fill: white !important;
|
||||
}
|
||||
|
||||
#secret .copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url("../gradientdeco-full.svg");
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
height: 44vw;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
/* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Support for IE. */
|
||||
font-feature-settings: "liga";
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: white;
|
||||
color: #282828;
|
||||
}
|
||||
html header .logo {
|
||||
background-image: url("../logo-light.png");
|
||||
}
|
||||
html h1 {
|
||||
color: #282828;
|
||||
}
|
||||
html button {
|
||||
background-color: white;
|
||||
color: #760038;
|
||||
border: 2px solid #760038;
|
||||
}
|
||||
html button:hover {
|
||||
background-color: #f60075;
|
||||
border: 2px solid #f60075;
|
||||
}
|
||||
html button.primary {
|
||||
background-color: #760038;
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0px 10px 30px #760038;
|
||||
}
|
||||
html button.primary:hover {
|
||||
background-color: #f60075;
|
||||
}
|
||||
html input {
|
||||
background-color: white;
|
||||
color: #282828;
|
||||
}
|
||||
html #qrcode svg rect[style*="fill:white"] {
|
||||
fill: white !important;
|
||||
}
|
||||
html #qrcode svg rect[style*="fill:black"] {
|
||||
fill: #282828 !important;
|
||||
}
|
||||
html footer {
|
||||
background-image: url("../gradientdeco-full.svg");
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=light.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/caos/variables.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCGW;EDFX;EACA;;;AAGJ;EACI;;;AAGJ;EACI,kBCDc;EDEd,OCDQ;;;ADIZ;EACI,OCLQ;EDMR,aCZS;EDaT;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCnCW;EDoCX;EACA;EACA;;AAEA;EACI,OCxCY;;;AD4CpB;EACI;EACA,kBCjDc;EDkDd,OChDW;EDiDX;EACA;EACA;EACA;EACA,QE/DU;EFgEV;EACA;EACA;;AACA;EACI,kBCzDY;ED0DZ;;AAGJ;EACI,kBC/DO;EDgEP,OCjEI;EDkEJ;;AACA;EACI,kBClEQ;;ADsEhB;EACI;;;AAIR;EACI,kBE7EmB;EF8EnB,OC/EQ;EDgFR,QEzFU;EF0FV;EACA;EACA;;;AAIA;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI,OE9GK;EF+GL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKJ;EACI;EACA;;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AG1MJ;EACI,kBFYQ;EEXR,OFUc;;AERd;EACI;;AAGJ;EACI,OFGU;;AEAd;EACI;EACA;EACA;;AAEA;EACI,kBFIa;EEHb;;AAGJ;EACI,kBFTG;EEUH,OFXA;EEYA;EACA;;AACA;EACI,kBFbI;;AEkBhB;EACI,kBFrBI;EEsBJ,OFvBU;;AE2BV;EACI;;AAGJ;EACI;;AAIR;EACI","file":"light.css"}
|
||||
BIN
internal/login/static/resources/themes/caos/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 361 KiB |
BIN
internal/login/static/resources/themes/caos/logo-dark.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
internal/login/static/resources/themes/caos/logo-light.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,3 @@
|
||||
@import "../variables.scss";
|
||||
@import "./variables.scss";
|
||||
@import "../main.scss";
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "../variables.scss";
|
||||
@import "./variables.scss";
|
||||
@import "../main.scss";
|
||||
@import "../light.scss";
|
||||
@@ -0,0 +1,24 @@
|
||||
$logoImgDark: "../logo-dark.png";
|
||||
$logoImgLight: "../logo-light.png";
|
||||
|
||||
$footerimgDark: "../gradientdeco-full.svg";
|
||||
$footerimgLight: "../gradientdeco-full.svg";
|
||||
|
||||
// ----- FONTS ------------
|
||||
$standardFont: Lato;
|
||||
$headerFont: Aileron;
|
||||
|
||||
// ----- COLORS ------------
|
||||
|
||||
// ------ DARK-THEME -------
|
||||
$backgroundColor: #282828;
|
||||
$fontColor: white;
|
||||
$primaryColor: #760038;
|
||||
$primaryColorHover: lighten($primaryColor, 25%);
|
||||
|
||||
|
||||
// ------ LIGHT-THEME -------
|
||||
$backgroundColorLight: $fontColor;
|
||||
$fontColorLight: $backgroundColor;
|
||||
$primaryColorLight: $primaryColor;
|
||||
$primaryColorHoverLight: lighten($primaryColorLight, 25%);
|
||||
84
internal/login/static/resources/themes/scss/fonts.scss
Normal file
@@ -0,0 +1,84 @@
|
||||
//Aileron
|
||||
@font-face {
|
||||
font-family: Aileron;
|
||||
src: url(../../../fonts/ailerons/ailerons.otf ) format('opentype');
|
||||
}
|
||||
|
||||
//Lato
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Thin.ttf ) format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-ThinItalic.ttf ) format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Light.ttf ) format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-LightItalic.ttf ) format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Regular.ttf ) format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Italic.ttf ) format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Bold.ttf ) format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BoldItalic.ttf ) format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Black.ttf ) format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BlackItalic.ttf ) format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
//Material Icons
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../../../fonts/material/MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(../../../fonts/material/MaterialIcons-Regular.woff2) format('woff2'),
|
||||
url(../../../fonts/material/MaterialIcons-Regular.woff) format('woff'),
|
||||
url(../../../fonts/material/MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
53
internal/login/static/resources/themes/scss/light.scss
Normal file
@@ -0,0 +1,53 @@
|
||||
// ---- LIGHT-THEME-------
|
||||
html {
|
||||
background-color: $backgroundColorLight;
|
||||
color: $fontColorLight;
|
||||
|
||||
header .logo {
|
||||
background-image: url($logoImgLight);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $fontColorLight;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $backgroundColorLight;
|
||||
color: $primaryColorLight;
|
||||
border: 2px solid $primaryColorLight;
|
||||
|
||||
&:hover {
|
||||
background-color: $primaryColorHoverLight;
|
||||
border: 2px solid $primaryColorHoverLight;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: $primaryColor;
|
||||
color: $fontColor;
|
||||
border: none;
|
||||
box-shadow: 0px 10px 30px $primaryColor;
|
||||
&:hover {
|
||||
background-color: $primaryColorHover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: $backgroundColorLight;
|
||||
color: $fontColorLight;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
svg rect[style*="fill:white"] {
|
||||
fill: $backgroundColorLight !important;
|
||||
}
|
||||
|
||||
svg rect[style*="fill:black"] {
|
||||
fill: $fontColorLight !important;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url($footerimgLight);
|
||||
}
|
||||
}
|
||||
205
internal/login/static/resources/themes/scss/main.scss
Normal file
@@ -0,0 +1,205 @@
|
||||
@import "fonts";
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
font-family: $standardFont;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: $backgroundColor;
|
||||
color: $fontColor;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $fontColor;
|
||||
font-family: $headerFont;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-width: 300;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 8px;
|
||||
|
||||
.logo {
|
||||
background-image: url($logoImgDark);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 80px;
|
||||
margin: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primaryColor;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
color: $primaryColorHover;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
background-color: $backgroundColor;
|
||||
color: $primaryColor;
|
||||
border: 2px solid $primaryColor;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: $inputHeight;
|
||||
transition: all 0.3s ease 0s;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
background-color: $primaryColorHover;
|
||||
border: 2px solid $primaryColorHover;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: $primaryColor;
|
||||
color: $fontColor;
|
||||
border: none;
|
||||
&:hover {
|
||||
background-color: $primaryColorHover;
|
||||
}
|
||||
}
|
||||
|
||||
& > .sessionstate {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
}
|
||||
|
||||
input:not([type='radio']), select {
|
||||
background-color: $inputBackgroundColor;
|
||||
color: $fontColor;
|
||||
height: $inputHeight;
|
||||
border: 2px solid $inputBorderColor;
|
||||
border-radius: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
form {
|
||||
.field {
|
||||
display: grid;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.field.radio-button {
|
||||
display: flex;
|
||||
|
||||
input[type='radio'] {
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& label {
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
padding: 3px 0 0 15px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: $labelColor;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 3px;
|
||||
|
||||
span.optional {
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 20px 0;
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
button, a {
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#copy-secret {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
text-align: center;
|
||||
|
||||
svg rect[style*="fill:white"] {
|
||||
fill: $backgroundColor !important;
|
||||
}
|
||||
|
||||
svg rect[style*="fill:black"] {
|
||||
fill: $fontColor !important;
|
||||
}
|
||||
}
|
||||
|
||||
#secret {
|
||||
.copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url($footerimgDark);
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
height: 44vw;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
23
internal/login/static/resources/themes/scss/variables.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
// ----- FONTS ------------
|
||||
$standardFont: Lato;
|
||||
$headerFont: Aileron;
|
||||
|
||||
// ----- LAYOUT ------------
|
||||
$inputHeight: 50px;
|
||||
|
||||
|
||||
// ----- DARK-THEME --------
|
||||
$backgroundColor: #282828;
|
||||
$fontColor: #FFFFFF;
|
||||
$primaryColor: #364DF6;
|
||||
$primaryColorHover: lighten($primaryColor, 10%);
|
||||
$labelColor: #898989;
|
||||
$inputBorderColor: #595959;
|
||||
$inputBackgroundColor: #252525;
|
||||
|
||||
|
||||
// ----- LIGHT-THEME --------
|
||||
$backgroundColorLight: $fontColor;
|
||||
$fontColorLight: $backgroundColor;
|
||||
$primaryColorLight: $primaryColor;
|
||||
$primaryColorHoverLight: lighten($primaryColorLight, 10%);
|
||||
@@ -0,0 +1,3 @@
|
||||
@import "../variables.scss";
|
||||
@import "./variables.scss";
|
||||
@import "../main.scss";
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "../variables.scss";
|
||||
@import "./variables.scss";
|
||||
@import "../main.scss";
|
||||
@import "../light.scss";
|
||||
@@ -0,0 +1,5 @@
|
||||
$logoImgDark: "../logo-dark.png";
|
||||
$logoImgLight: "../logo-light.png";
|
||||
|
||||
$footerimgDark: "../gradientdeco-full.svg";
|
||||
$footerimgLight: "../gradientdeco-full.svg";
|
||||
257
internal/login/static/resources/themes/zitadel/css/dark.css
Normal file
@@ -0,0 +1,257 @@
|
||||
@font-face {
|
||||
font-family: Aileron;
|
||||
src: url(../../../fonts/ailerons/ailerons.otf) format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Thin.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-ThinItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Light.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-LightItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Regular.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Italic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Bold.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BoldItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Black.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BlackItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Material Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../../../fonts/material/MaterialIcons-Regular.eot);
|
||||
/* For IE6-8 */
|
||||
src: local("Material Icons"), local("MaterialIcons-Regular"), url(../../../fonts/material/MaterialIcons-Regular.woff2) format("woff2"), url(../../../fonts/material/MaterialIcons-Regular.woff) format("woff"), url(../../../fonts/material/MaterialIcons-Regular.ttf) format("truetype");
|
||||
}
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
font-family: Lato;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #282828;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #FFFFFF;
|
||||
font-family: Aileron;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-width: 300;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 8px;
|
||||
}
|
||||
header .logo {
|
||||
background-image: url("../logo-dark.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 80px;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #364DF6;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
a:hover {
|
||||
color: #6778f8;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
background-color: #282828;
|
||||
color: #364DF6;
|
||||
border: 2px solid #364DF6;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: 50px;
|
||||
transition: all 0.3s ease 0s;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #6778f8;
|
||||
border: 2px solid #6778f8;
|
||||
}
|
||||
button.primary {
|
||||
background-color: #364DF6;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
}
|
||||
button.primary:hover {
|
||||
background-color: #6778f8;
|
||||
}
|
||||
button > .sessionstate {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
input:not([type=radio]), select {
|
||||
background-color: #252525;
|
||||
color: #FFFFFF;
|
||||
height: 50px;
|
||||
border: 2px solid #595959;
|
||||
border-radius: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
form .field {
|
||||
display: grid;
|
||||
padding: 10px 0;
|
||||
}
|
||||
form .field.radio-button {
|
||||
display: flex;
|
||||
}
|
||||
form .field.radio-button input[type=radio] {
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
form .field.radio-button label {
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
padding: 3px 0 0 15px;
|
||||
width: 100%;
|
||||
}
|
||||
form label {
|
||||
color: #898989;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
form label span.optional {
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
}
|
||||
form .actions {
|
||||
padding: 20px 0;
|
||||
}
|
||||
form .actions .right {
|
||||
float: right;
|
||||
}
|
||||
form .actions button, form .actions a {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#copy-secret {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
text-align: center;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:white"] {
|
||||
fill: #282828 !important;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:black"] {
|
||||
fill: #FFFFFF !important;
|
||||
}
|
||||
|
||||
#secret .copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url("../gradientdeco-full.svg");
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
height: 44vw;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
/* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Support for IE. */
|
||||
font-feature-settings: "liga";
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=dark.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI,kBCLc;EDMd,OCLQ;;;ADQZ;EACI,OCTQ;EDUR,aClBS;EDmBT;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvCW;EDwCX;EACA;EACA;;AAEA;EACI,OC5CY;;;ADgDpB;EACI;EACA,kBCrDc;EDsDd,OCpDW;EDqDX;EACA;EACA;EACA;EACA,QC/DU;EDgEV;EACA;EACA;;AACA;EACI,kBC7DY;ED8DZ;;AAGJ;EACI,kBCnEO;EDoEP,OCrEI;EDsEJ;;AACA;EACI,kBCtEQ;;AD0EhB;EACI;;;AAIR;EACI,kBC7EmB;ED8EnB,OCnFQ;EDoFR,QCzFU;ED0FV;EACA;EACA;;;AAIA;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI,OC9GK;ED+GL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKJ;EACI;EACA;;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA","file":"dark.css"}
|
||||
299
internal/login/static/resources/themes/zitadel/css/light.css
Normal file
@@ -0,0 +1,299 @@
|
||||
@font-face {
|
||||
font-family: Aileron;
|
||||
src: url(../../../fonts/ailerons/ailerons.otf) format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Thin.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-ThinItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Light.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-LightItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Regular.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Italic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Bold.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BoldItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-Black.ttf) format("truetype");
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url(../../../fonts/lato/Lato-BlackItalic.ttf) format("truetype");
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Material Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(../../../fonts/material/MaterialIcons-Regular.eot);
|
||||
/* For IE6-8 */
|
||||
src: local("Material Icons"), local("MaterialIcons-Regular"), url(../../../fonts/material/MaterialIcons-Regular.woff2) format("woff2"), url(../../../fonts/material/MaterialIcons-Regular.woff) format("woff"), url(../../../fonts/material/MaterialIcons-Regular.ttf) format("truetype");
|
||||
}
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
font-family: Lato;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #282828;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #FFFFFF;
|
||||
font-family: Aileron;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-width: 300;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 8px;
|
||||
}
|
||||
header .logo {
|
||||
background-image: url("../logo-dark.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 80px;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #364DF6;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
a:hover {
|
||||
color: #6778f8;
|
||||
}
|
||||
|
||||
button {
|
||||
text-transform: uppercase;
|
||||
background-color: #282828;
|
||||
color: #364DF6;
|
||||
border: 2px solid #364DF6;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: 50px;
|
||||
transition: all 0.3s ease 0s;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #6778f8;
|
||||
border: 2px solid #6778f8;
|
||||
}
|
||||
button.primary {
|
||||
background-color: #364DF6;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
}
|
||||
button.primary:hover {
|
||||
background-color: #6778f8;
|
||||
}
|
||||
button > .sessionstate {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
input:not([type=radio]), select {
|
||||
background-color: #252525;
|
||||
color: #FFFFFF;
|
||||
height: 50px;
|
||||
border: 2px solid #595959;
|
||||
border-radius: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
form .field {
|
||||
display: grid;
|
||||
padding: 10px 0;
|
||||
}
|
||||
form .field.radio-button {
|
||||
display: flex;
|
||||
}
|
||||
form .field.radio-button input[type=radio] {
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
form .field.radio-button label {
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
padding: 3px 0 0 15px;
|
||||
width: 100%;
|
||||
}
|
||||
form label {
|
||||
color: #898989;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
form label span.optional {
|
||||
font-style: italic;
|
||||
text-transform: none;
|
||||
}
|
||||
form .actions {
|
||||
padding: 20px 0;
|
||||
}
|
||||
form .actions .right {
|
||||
float: right;
|
||||
}
|
||||
form .actions button, form .actions a {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#copy-secret {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
text-align: center;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:white"] {
|
||||
fill: #282828 !important;
|
||||
}
|
||||
#qrcode svg rect[style*="fill:black"] {
|
||||
fill: #FFFFFF !important;
|
||||
}
|
||||
|
||||
#secret .copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-image: url("../gradientdeco-full.svg");
|
||||
width: 100%;
|
||||
background-size: cover;
|
||||
height: 44vw;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: "Material Icons";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
/* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Support for IE. */
|
||||
font-feature-settings: "liga";
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #FFFFFF;
|
||||
color: #282828;
|
||||
}
|
||||
html header .logo {
|
||||
background-image: url("../logo-light.png");
|
||||
}
|
||||
html h1 {
|
||||
color: #282828;
|
||||
}
|
||||
html button {
|
||||
background-color: #FFFFFF;
|
||||
color: #364DF6;
|
||||
border: 2px solid #364DF6;
|
||||
}
|
||||
html button:hover {
|
||||
background-color: #6778f8;
|
||||
border: 2px solid #6778f8;
|
||||
}
|
||||
html button.primary {
|
||||
background-color: #364DF6;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
box-shadow: 0px 10px 30px #364DF6;
|
||||
}
|
||||
html button.primary:hover {
|
||||
background-color: #6778f8;
|
||||
}
|
||||
html input {
|
||||
background-color: #FFFFFF;
|
||||
color: #282828;
|
||||
}
|
||||
html #qrcode svg rect[style*="fill:white"] {
|
||||
fill: #FFFFFF !important;
|
||||
}
|
||||
html #qrcode svg rect[style*="fill:black"] {
|
||||
fill: #282828 !important;
|
||||
}
|
||||
html footer {
|
||||
background-image: url("../gradientdeco-full.svg");
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=light.css.map */
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../scss/fonts.scss","../../scss/main.scss","../../scss/variables.scss","../../scss/light.scss"],"names":[],"mappings":"AACA;EACI;EACA;;AAIJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAIJ;EACI;EACA;EACA;EACA;AAA6D;EAC7D;;AC5EJ;EACI;EACA,aCHW;EDIX;EACA;;;AAGJ;EACI;;;AAGJ;EACI,kBCLc;EDMd,OCLQ;;;ADQZ;EACI,OCTQ;EDUR,aClBS;EDmBT;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;;AAGJ;EACI,OCvCW;EDwCX;EACA;EACA;;AAEA;EACI,OC5CY;;;ADgDpB;EACI;EACA,kBCrDc;EDsDd,OCpDW;EDqDX;EACA;EACA;EACA;EACA,QC/DU;EDgEV;EACA;EACA;;AACA;EACI,kBC7DY;ED8DZ;;AAGJ;EACI,kBCnEO;EDoEP,OCrEI;EDsEJ;;AACA;EACI,kBCtEQ;;AD0EhB;EACI;;;AAIR;EACI,kBC7EmB;ED8EnB,OCnFQ;EDoFR,QCzFU;ED0FV;EACA;EACA;;;AAIA;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAIR;EACI,OC9GK;ED+GL;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKZ;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;;AAGJ;EACI;;;AAKJ;EACI;EACA;;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;AAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;AACA;EACA;AAEA;EACA;AAEA;EACA;;;AE1MJ;EACI,kBDQQ;ECPR,ODMc;;ACJd;EACI;;AAGJ;EACI,ODDU;;ACId;EACI,kBDJI;ECKJ,ODJO;ECKP;;AAEA;EACI,kBDGa;ECFb;;AAGJ;EACI,kBDbG;ECcH,ODfA;ECgBA;EACA;;AACA;EACI,kBDjBI;;ACsBhB;EACI,kBDzBI;EC0BJ,OD3BU;;AC+BV;EACI;;AAGJ;EACI;;AAIR;EACI","file":"light.css"}
|
||||
BIN
internal/login/static/resources/themes/zitadel/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 361 KiB |
BIN
internal/login/static/resources/themes/zitadel/logo-dark.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
internal/login/static/resources/themes/zitadel/logo-light.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
32
internal/login/static/templates/change_password.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "PasswordChange.Title"}}</h1>
|
||||
<p>{{t "PasswordChange.Description"}}</p>
|
||||
|
||||
<form action="{{ changePasswordUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="old_password">{{t "PasswordChange.OldPassword"}}</label>
|
||||
<input class="input" type="password" id="old_password" name="old_password" autocomplete="current-password" autofocus required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="new_password">{{t "PasswordChange.NewPassword"}}</label>
|
||||
<input class="input" type="password" id="new-password" name="new_password" autocomplete="new-password" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</buttontype="submit">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
19
internal/login/static/templates/change_password_done.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "PasswordChangeDone.Title"}}</h1>
|
||||
<p>{{t "PasswordChangeDone.Description"}}</p>
|
||||
|
||||
<form action="{{ loginUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
9
internal/login/static/templates/error-message.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{{ define "error-message" }}
|
||||
{{if .ErrMessage }}
|
||||
<div class="field">
|
||||
<div class="error">
|
||||
{{ if .ErrType }}{{ .ErrType }} - {{end}}{{ .ErrMessage }}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
9
internal/login/static/templates/error.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
<div>
|
||||
{{ .ErrType }}
|
||||
{{ .ErrMessage }}
|
||||
</div>
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
3
internal/login/static/templates/footer.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{{define "footer"}}
|
||||
|
||||
{{end}}
|
||||
3
internal/login/static/templates/header.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{{define "header"}}
|
||||
<div class="logo"></div>
|
||||
{{end}}selec
|
||||
37
internal/login/static/templates/init_password.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "InitPassword.Title" }}</h1>
|
||||
<p>{{t "InitPassword.Description" }}</p>
|
||||
|
||||
<form action="{{ initPasswordUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="userID" value="{{ .UserID }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="code">{{t "InitPassword.Code"}}</label>
|
||||
<input class="input" type="text" id="code" name="code" value="{{.Code}}" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="password">{{t "InitPassword.NewPassword"}}</label>
|
||||
<input class="input" type="password" id="password" name="password" autocomplete="new-password" autofocus required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="passwordconfirm">{{t "InitPassword.NewPasswordConfirm"}}</label>
|
||||
<input class="input" type="password" id="passwordconfirm" name="passwordconfirm" autocomplete="new-password" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</buttontype="submit">
|
||||
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
17
internal/login/static/templates/init_password_done.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "PasswordSetDone.Title"}}</h1>
|
||||
<p>{{t "PasswordSetDone.Description"}}</p>
|
||||
<form action="{{ loginUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
37
internal/login/static/templates/init_user.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "InitUser.Title" }}</h1>
|
||||
<p>{{t "InitUser.Description" }}</p>
|
||||
|
||||
<form action="{{ initUserUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="userID" value="{{ .UserID }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="code">{{t "InitUser.Code"}}</label>
|
||||
<input class="input" type="text" id="code" name="code" value="{{.Code}}" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="password">{{t "InitUser.NewPassword"}}</label>
|
||||
<input class="input" type="password" id="password" name="password" autocomplete="new-password" autofocus required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="passwordconfirm">{{t "InitUser.NewPasswordConfirm"}}</label>
|
||||
<input class="input" type="password" id="passwordconfirm" name="passwordconfirm" autocomplete="new-password" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</buttontype="submit">
|
||||
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
17
internal/login/static/templates/init_user_done.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "InitUserDone.Title"}}</h1>
|
||||
<p>{{t "InitUserDone.Description"}}</p>
|
||||
<form action="{{ loginUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
27
internal/login/static/templates/login.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
<h1>{{t "Login.Title"}}</h1>
|
||||
<p>{{t "Login.Description"}}</p>
|
||||
|
||||
<form action="{{ usernameUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="username">{{t "Login.Username"}}</label>
|
||||
<input class="input" type="text" id="username" name="username" value="{{ .UserName }}" autocomplete="username" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
<a class="default right" href="{{ registerUrl .AuthReqID }}" >{{t "Actions.Register"}}</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
15
internal/login/static/templates/logout_done.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "LogoutDone.Title"}}</h1>
|
||||
<p>{{t "LogoutDone.Description"}}</p>
|
||||
<form action="{{ loginUrl }}" method="POST">
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Login"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
31
internal/login/static/templates/mail_verification.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "EmailVerification.Title"}}</h1>
|
||||
<p>{{t "EmailVerification.Description"}}</p>
|
||||
|
||||
<form action="{{ mailVerificationUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="userID" value="{{ .UserID }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="code">{{t "EmailVerification.Code"}}</label>
|
||||
<input class="input" type="text" id="code" name="code" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" name="resend" value="false" class="primary right" >{{t "Actions.Next"}}</buttontype="submit">
|
||||
{{ if .UserID }}
|
||||
<button type="submit" name="resend" value="true" class="secondary right" formnovalidate>{{t "Actions.Resend"}}</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
19
internal/login/static/templates/mail_verified.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "EmailVerificationDone.Title"}}</h1>
|
||||
<p>{{t "EmailVerificationDone.Description"}}</p>
|
||||
|
||||
<form action="{{ loginUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{if .AuthReqID }}{{t "Actions.Next"}}{{else}}{{t "Actions.Login"}}{{end}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
34
internal/login/static/templates/main.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{{define "main-top"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Lang }}" class="{{.ThemeMode}}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
{{if .ThemeMode}}
|
||||
<link rel="stylesheet" href="{{ resourceThemeUrl (printf "css/%s.css" .ThemeMode) .Theme }}" type="text/css" media="all">
|
||||
{{else}}
|
||||
<link rel="stylesheet" href="{{ resourceThemeUrl "css/dark.css" .Theme }}" type="text/css" media="(prefers-color-scheme: dark), (prefers-color-scheme: no-preference)">
|
||||
<link rel="stylesheet" href="{{ resourceThemeUrl "css/light.css" .Theme }}" type="text/css" media="(prefers-color-scheme: light)">
|
||||
{{end}}
|
||||
<link rel="icon" type="image/x-icon" href="{{ resourceThemeUrl "favicon.ico" .Theme }}">
|
||||
|
||||
<title>{{ .Title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
{{template "header" .}}
|
||||
</header>
|
||||
<div class="content">
|
||||
{{end}}
|
||||
|
||||
<!-- here goes the content -->
|
||||
|
||||
{{define "main-bottom"}}
|
||||
</div>
|
||||
</body>
|
||||
<footer>
|
||||
{{template "footer" .}}
|
||||
</footer>
|
||||
{{end}}
|
||||
19
internal/login/static/templates/mfa_init_done.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "MfaInitDone.Title"}}</h1>
|
||||
<p>{{t "MfaInitDone.Description"}}</p>
|
||||
|
||||
<form action="{{ loginUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="mfaType" value="{{ .MfaType }}" />
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
47
internal/login/static/templates/mfa_init_verify.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "MfaInitVerify.Title"}}</h1>
|
||||
<p>{{t "MfaInitVerify.Description"}}</p>
|
||||
|
||||
<form action="{{ mfaInitVerifyUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="mfaType" value="{{ .MfaType }}" />
|
||||
<input type="hidden" name="url" value="{{ .Url }}" />
|
||||
<input type="hidden" name="secret" value="{{ .Secret }}" />
|
||||
|
||||
{{if (eq .MfaType 0) }}
|
||||
<p>{{t "MfaInitVerify.OtpDescription"}}</p>
|
||||
<div id="qrcode">
|
||||
{{.QrCode}}
|
||||
</div>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<span class="label" for="secret">{{t "MfaInitVerify.Secret"}}</span>
|
||||
<span class="input" id="secret">
|
||||
{{.Secret}}
|
||||
<span class="copy material-icons" onclick="copyToClipboard('{{ .Secret }}')">content_copy</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="code">{{t "MfaInitVerify.Code"}}</label>
|
||||
<input class="input" type="text" id="code" name="code" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const copyToClipboard = str => {
|
||||
navigator.clipboard.writeText(str);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
30
internal/login/static/templates/mfa_prompt.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "MfaPrompt.Title"}}</h1>
|
||||
|
||||
<form action="{{ mfaPromptUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="fields">
|
||||
{{ range $provider := .MfaProviders}}
|
||||
{{ $providerName := (t (printf "MfaPrompt.Provider%v" $provider)) }}
|
||||
<div class="field radio-button">
|
||||
<input id="{{ $provider }}" type="radio" name="provider" value="{{ $provider }}">
|
||||
<label for="{{ $provider }}">{{ $providerName }}</label>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
{{if not .MfaRequired}}
|
||||
<button class="default right" name="skip" value="true" type="submit" formnovalidate>{{t "Actions.Skip"}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
28
internal/login/static/templates/mfa_verify.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "MfaVerify.Title"}}</h1>
|
||||
<p>{{t "MfaVerify.Description"}}</p>
|
||||
|
||||
<form action="{{ mfaVerifyUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="mfaType" value="{{ .SelectedMfaProvider }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="code">{{t "MfaVerify.Code"}}</label>
|
||||
<input class="input" type="text" id="code" name="code" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
33
internal/login/static/templates/password.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "Password.Title"}}</h1>
|
||||
|
||||
<form action="{{ passwordUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
<input type="hidden" name="username" value="{{ .UserName }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="password">{{t "Password.Password"}}</label>
|
||||
<input class="input" type="password" id="password" name="password" autocomplete="current-password" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
<a href="{{ usernameChangeUrl .AuthReqID }}">
|
||||
<button class="secondary" type="button">{{t "Actions.Back"}}</button>
|
||||
</a>
|
||||
<a href="{{ passwordResetUrl .AuthReqID }}">
|
||||
<button class="secondary" type="button">{{t "Actions.ForgotPassword"}}</button>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
17
internal/login/static/templates/password_reset_done.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "PasswordResetDone.Title"}}</h1>
|
||||
<p>{{t "PasswordResetDone.Description"}}</p>
|
||||
<form action="{{ loginUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
61
internal/login/static/templates/register.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
<h1>{{t "Registration.Title"}}</h1>
|
||||
<p>{{t "Registration.Description"}}</p>
|
||||
|
||||
<form action="{{ registrationUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label class="label" for="email">{{t "Registration.Email"}}</label>
|
||||
<input class="input" type="text" id="email" name="email" autocomplete="email" value="{{ .Email }}" autofocus required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="firstname">{{t "Registration.Firstname"}}</label>
|
||||
<input class="input" type="text" id="firstname" name="firstname" autocomplete="given-name" value="{{ .Firstname }}" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="lastname">{{t "Registration.Lastname"}}</label>
|
||||
<input class="input" type="text" id="lastname" name="lastname" autocomplete="family-name" value="{{ .Lastname }}" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="languages">{{t "Registration.Language"}}</label>
|
||||
<select id="languages" name="language">
|
||||
<option value=""></option>
|
||||
<option value="de" id="de" {{if (selectedLanguage "de")}} selected {{end}}>{{t "Registration.German"}}</option>
|
||||
<option value="en" id="en" {{if (selectedLanguage "en")}} selected {{end}}>{{t "Registration.English"}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="genders">
|
||||
{{t "Registration.Gender"}}
|
||||
<span class="optional">{{t "optional"}}</span>
|
||||
</label>
|
||||
<select id="genders" name="gender">
|
||||
<option value=""></option>
|
||||
<option value="1" id="female" {{if (selectedGender 1)}} selected {{end}}>{{t "Registration.Female"}}</option>
|
||||
<option value="2" id="male" {{if (selectedGender 2)}} selected {{end}}>{{t "Registration.Male"}}</option>
|
||||
<option value="3" id="diverse" {{if (selectedGender 3)}} selected {{end}}>{{t "Registration.Diverse"}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="password">{{t "Registration.Password"}}</label>
|
||||
<input class="input" type="password" id="password" name="password" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="password2">{{t "Registration.Password2"}}</label>
|
||||
<input class="input" type="password" id="password2" name="password2" autocomplete="new-password" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "error-message" .}}
|
||||
|
||||
<div class="actions">
|
||||
<button class="primary right" type="submit">{{t "Actions.Next"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
25
internal/login/static/templates/select_user.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{{template "main-top" .}}
|
||||
|
||||
|
||||
<h1>{{t "UserSelection.Title"}}</h1>
|
||||
<p>{{t "UserSelection.Description"}}</p>
|
||||
|
||||
<form action="{{ userSelectionUrl }}" method="POST">
|
||||
|
||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||
|
||||
<div class="actions">
|
||||
{{ range $user := .Users }}
|
||||
{{ $sessionState := (t (printf "UserSelection.SessionState%v" $user.UserSessionState)) }}
|
||||
<button type="submit" name="userID" value="{{$user.UserID}}" class="primary">
|
||||
<span class="username">{{$user.UserName}}</span>
|
||||
<span class="sessionstate">({{$sessionState}})</span>
|
||||
</button>
|
||||
{{ end }}
|
||||
<button type="submit" name="userID" value="0" class="primary">{{t "UserSelection.OtherUser"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{{template "main-bottom" .}}
|
||||
|
||||
3
internal/login/statik/generate.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package statik
|
||||
|
||||
//go:generate statik -src=../static -dest=.. -ns=login
|
||||