Add NO_AUTH configuration option.
This option permit to use happyDNS without creating accounts.
This commit is contained in:
parent
4dc0c592d0
commit
e80281c941
|
@ -211,7 +211,7 @@ type RequestResources struct {
|
|||
Zone *happydns.Zone
|
||||
}
|
||||
|
||||
func apiAuthHandler(f func(*config.Options, *RequestResources, io.Reader) Response) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
func apiOptionalAuthHandler(noauthcb func(*config.Options, *RequestResources, io.Reader) Response, authcb func(*config.Options, *RequestResources, io.Reader) Response) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
|
||||
r.RemoteAddr = addr
|
||||
|
@ -253,48 +253,61 @@ func apiAuthHandler(f func(*config.Options, *RequestResources, io.Reader) Respon
|
|||
}
|
||||
}
|
||||
|
||||
if sessionid == nil || len(sessionid) == 0 {
|
||||
logResponse(r, APIErrorResponse{
|
||||
err: fmt.Errorf("Authorization required"),
|
||||
status: http.StatusUnauthorized,
|
||||
}.WriteResponse(w))
|
||||
} else if session, err := storage.MainStore.GetSession(sessionid); err != nil {
|
||||
logResponse(r, APIErrorResponse{
|
||||
err: err,
|
||||
status: http.StatusUnauthorized,
|
||||
cookies: []*http.Cookie{&http.Cookie{
|
||||
Name: "happydns_session",
|
||||
Value: "",
|
||||
Path: opts.BaseURL + "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
Secure: opts.DevProxy == "",
|
||||
HttpOnly: true,
|
||||
}},
|
||||
}.WriteResponse(w))
|
||||
} else if user, err := storage.MainStore.GetUser(session.IdUser); err != nil {
|
||||
logResponse(r, APIErrorResponse{
|
||||
err: err,
|
||||
status: http.StatusUnauthorized,
|
||||
cookies: []*http.Cookie{&http.Cookie{
|
||||
Name: "happydns_session",
|
||||
Value: "",
|
||||
Path: opts.BaseURL + "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
Secure: opts.DevProxy == "",
|
||||
HttpOnly: true,
|
||||
}},
|
||||
}.WriteResponse(w))
|
||||
} else {
|
||||
req := &RequestResources{
|
||||
Ps: ps,
|
||||
Session: session,
|
||||
User: user,
|
||||
}
|
||||
logResponse(r, f(opts, req, r.Body).WriteResponse(w))
|
||||
var err error
|
||||
req := &RequestResources{
|
||||
Ps: ps,
|
||||
}
|
||||
|
||||
if session.HasChanged() {
|
||||
storage.MainStore.UpdateSession(session)
|
||||
}
|
||||
if sessionid == nil || len(sessionid) == 0 {
|
||||
logResponse(r, noauthcb(opts, req, r.Body).WriteResponse(w))
|
||||
} else if req.Session, err = storage.MainStore.GetSession(sessionid); err != nil {
|
||||
logResponse(r, APIErrorResponse{
|
||||
err: err,
|
||||
status: http.StatusUnauthorized,
|
||||
cookies: []*http.Cookie{&http.Cookie{
|
||||
Name: "happydns_session",
|
||||
Value: "",
|
||||
Path: opts.BaseURL + "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
Secure: opts.DevProxy == "",
|
||||
HttpOnly: true,
|
||||
}},
|
||||
}.WriteResponse(w))
|
||||
} else if req.User, err = storage.MainStore.GetUser(req.Session.IdUser); err != nil {
|
||||
logResponse(r, APIErrorResponse{
|
||||
err: err,
|
||||
status: http.StatusUnauthorized,
|
||||
cookies: []*http.Cookie{&http.Cookie{
|
||||
Name: "happydns_session",
|
||||
Value: "",
|
||||
Path: opts.BaseURL + "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
Secure: opts.DevProxy == "",
|
||||
HttpOnly: true,
|
||||
}},
|
||||
}.WriteResponse(w))
|
||||
} else if req.User.Email == NO_AUTH_ACCOUNT && !opts.NoAuth {
|
||||
logResponse(r, noauthcb(opts, req, r.Body).WriteResponse(w))
|
||||
} else {
|
||||
logResponse(r, authcb(opts, req, r.Body).WriteResponse(w))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func apiAuthHandler(f func(*config.Options, *RequestResources, io.Reader) Response) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return apiOptionalAuthHandler(func(opts *config.Options, req *RequestResources, _ io.Reader) Response {
|
||||
return APIErrorResponse{
|
||||
err: fmt.Errorf("Authorization required"),
|
||||
status: http.StatusUnauthorized,
|
||||
}
|
||||
}, func(opts *config.Options, req *RequestResources, r io.Reader) Response {
|
||||
response := f(opts, req, r)
|
||||
|
||||
if req.Session.HasChanged() {
|
||||
storage.MainStore.UpdateSession(req.Session)
|
||||
}
|
||||
|
||||
return response
|
||||
})
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -47,10 +48,12 @@ import (
|
|||
"git.happydns.org/happydns/storage"
|
||||
)
|
||||
|
||||
const NO_AUTH_ACCOUNT = "_no_auth"
|
||||
|
||||
var AuthFunc = checkAuth
|
||||
|
||||
func init() {
|
||||
router.GET("/api/auth", apiAuthHandler(displayAuthToken))
|
||||
router.GET("/api/auth", apiOptionalAuthHandler(displayNotAuthToken, displayAuthToken))
|
||||
router.POST("/api/auth", ApiHandler(func(opts *config.Options, ps httprouter.Params, b io.Reader) Response {
|
||||
return AuthFunc(opts, ps, b)
|
||||
}))
|
||||
|
@ -73,6 +76,17 @@ func currentUser(u *happydns.User) *DisplayUser {
|
|||
}
|
||||
}
|
||||
|
||||
func displayNotAuthToken(opts *config.Options, req *RequestResources, _ io.Reader) Response {
|
||||
if opts.NoAuth {
|
||||
return completeAuth(opts, NO_AUTH_ACCOUNT, NO_AUTH_ACCOUNT)
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
err: fmt.Errorf("Authorization required"),
|
||||
status: http.StatusUnauthorized,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func displayAuthToken(_ *config.Options, req *RequestResources, _ io.Reader) Response {
|
||||
return APIResponse{
|
||||
response: currentUser(req.User),
|
||||
|
|
|
@ -151,7 +151,7 @@ func registerUser(opts *config.Options, p httprouter.Params, body io.Reader) Res
|
|||
|
||||
if len(uu.Password) <= 7 {
|
||||
return APIErrorResponse{
|
||||
err: errors.New("The given email is invalid."),
|
||||
err: errors.New("The given password is invalid."),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ func (o *Options) declareFlags() {
|
|||
flag.StringVar(&o.BaseURL, "baseurl", o.BaseURL, "URL prepended to each URL")
|
||||
flag.StringVar(&o.DefaultNameServer, "default-ns", o.DefaultNameServer, "Adress to the default name server")
|
||||
flag.Var(&o.StorageEngine, "storage-engine", fmt.Sprintf("Select the storage engine between %v", storage.GetStorageEngines()))
|
||||
flag.BoolVar(&o.NoAuth, "no-auth", false, "Disable user access control, use default account")
|
||||
|
||||
// Others flags are declared in some other files likes sources, storages, ... when they need specials configurations
|
||||
}
|
||||
|
|
|
@ -66,6 +66,9 @@ type Options struct {
|
|||
|
||||
// StorageEngine points to the storage engine used.
|
||||
StorageEngine storage.StorageEngine
|
||||
|
||||
// NoAuth controls if there is user access control or not.
|
||||
NoAuth bool
|
||||
}
|
||||
|
||||
// BuildURL appends the given url to the absolute ExternalURL.
|
||||
|
|
|
@ -51,9 +51,12 @@
|
|||
|
||||
<b-nav-item-dropdown v-if="user_isLogged" right>
|
||||
<template slot="button-content">
|
||||
<b-button size="sm" variant="dark">
|
||||
<b-button v-if="user_getSession.email !== '_no_auth'" size="sm" variant="dark">
|
||||
<b-icon icon="person" aria-hidden="true" /> {{ user_getSession.email }}
|
||||
</b-button>
|
||||
<b-button v-else size="sm" variant="secondary">
|
||||
{{ $t('menu.quick-menu') }}
|
||||
</b-button>
|
||||
</template>
|
||||
<b-dropdown-item to="/domains/">
|
||||
{{ $t('menu.my-domains') }}
|
||||
|
@ -69,8 +72,8 @@
|
|||
<b-dropdown-item to="/me">
|
||||
{{ $t('menu.my-account') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-divider />
|
||||
<b-dropdown-item @click="logout()">
|
||||
<b-dropdown-divider v-if="user_getSession.email !== '_no_auth'" />
|
||||
<b-dropdown-item v-if="user_getSession.email !== '_no_auth'" @click="logout()">
|
||||
{{ $t('menu.logout') }}
|
||||
</b-dropdown-item>
|
||||
</b-nav-item-dropdown>
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
"content": "The page you are look for was not found."
|
||||
},
|
||||
"account-delete": "An error occurs when trying to delete your account",
|
||||
"account-no-auth": "You're using happyDNS without authentication. You cannot manage other account properties.",
|
||||
"address": "Email address is required",
|
||||
"address-valid": "A valid email address is required",
|
||||
"domain-access": "An error occurs when trying to access domain's list.",
|
||||
|
@ -173,6 +174,7 @@
|
|||
"dns-resolver": "DNS resolver",
|
||||
"my-account": "My account",
|
||||
"logout": "Logout",
|
||||
"quick-menu": "Quick Access",
|
||||
"signup": "Sign up",
|
||||
"signin": "Sign in"
|
||||
},
|
||||
|
|
|
@ -63,85 +63,90 @@
|
|||
</b-form>
|
||||
</b-card>
|
||||
</b-row>
|
||||
<h2 id="password-change">
|
||||
{{ $t('password.change') }}
|
||||
</h2>
|
||||
<b-row>
|
||||
<b-card class="offset-md-2 col-8">
|
||||
<b-form @submit.stop.prevent="sendChPassword">
|
||||
<b-form-group
|
||||
:label="$t('password.enter')"
|
||||
label-for="currentPassword-input"
|
||||
>
|
||||
<b-form-input
|
||||
id="currentPassword-input"
|
||||
v-model="signupForm.current"
|
||||
type="password"
|
||||
required
|
||||
placeholder="xXxXxXxXxX"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
:state="passwordState"
|
||||
:label="$t('password.enter-new')"
|
||||
label-for="password-input"
|
||||
:invalid-feedback="$t('errors.password-weak')"
|
||||
>
|
||||
<b-form-input
|
||||
id="password-input"
|
||||
ref="signuppassword"
|
||||
v-model="signupForm.password"
|
||||
type="password"
|
||||
<div v-if="loggedUser && loggedUser.email !== '_no_auth'">
|
||||
<h2 id="password-change">
|
||||
{{ $t('password.change') }}
|
||||
</h2>
|
||||
<b-row>
|
||||
<b-card class="offset-md-2 col-8">
|
||||
<b-form @submit.stop.prevent="sendChPassword">
|
||||
<b-form-group
|
||||
:label="$t('password.enter')"
|
||||
label-for="currentPassword-input"
|
||||
>
|
||||
<b-form-input
|
||||
id="currentPassword-input"
|
||||
v-model="signupForm.current"
|
||||
type="password"
|
||||
required
|
||||
placeholder="xXxXxXxXxX"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
:state="passwordState"
|
||||
required
|
||||
placeholder="xXxXxXxXxX"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
:state="passwordConfirmState"
|
||||
:label="$t('password.confirm-new')"
|
||||
label-for="passwordconfirm-input"
|
||||
:invalid-feedback="$t('errors.password-match')"
|
||||
>
|
||||
<b-form-input
|
||||
id="passwordconfirm-input"
|
||||
ref="signuppasswordconfirm"
|
||||
v-model="signupForm.passwordConfirm"
|
||||
type="password"
|
||||
:label="$t('password.enter-new')"
|
||||
label-for="password-input"
|
||||
:invalid-feedback="$t('errors.password-weak')"
|
||||
>
|
||||
<b-form-input
|
||||
id="password-input"
|
||||
ref="signuppassword"
|
||||
v-model="signupForm.password"
|
||||
type="password"
|
||||
:state="passwordState"
|
||||
required
|
||||
placeholder="xXxXxXxXxX"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
:state="passwordConfirmState"
|
||||
required
|
||||
placeholder="xXxXxXxXxX"
|
||||
/>
|
||||
</b-form-group>
|
||||
<div class="d-flex justify-content-around">
|
||||
<b-button type="submit" variant="primary">
|
||||
{{ $t('password.change') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-form>
|
||||
</b-card>
|
||||
</b-row>
|
||||
<hr>
|
||||
<h2 id="delete-account">
|
||||
{{ $t('account.delete.delete') }}
|
||||
</h2>
|
||||
<b-row>
|
||||
<b-card class="offset-md-2 col-8">
|
||||
<p>
|
||||
{{ $t('account.delete.confirm') }}
|
||||
</p>
|
||||
<b-button type="button" variant="danger" @click="askAccountDeletion">
|
||||
{{ $t('account.delete.delete') }}
|
||||
</b-button>
|
||||
<p class="mt-2 text-muted" style="line-height: 1.1">
|
||||
<small>
|
||||
{{ $t('account.delete.consequence') }}
|
||||
</small>
|
||||
</p>
|
||||
</b-card>
|
||||
</b-row>
|
||||
:label="$t('password.confirm-new')"
|
||||
label-for="passwordconfirm-input"
|
||||
:invalid-feedback="$t('errors.password-match')"
|
||||
>
|
||||
<b-form-input
|
||||
id="passwordconfirm-input"
|
||||
ref="signuppasswordconfirm"
|
||||
v-model="signupForm.passwordConfirm"
|
||||
type="password"
|
||||
:state="passwordConfirmState"
|
||||
required
|
||||
placeholder="xXxXxXxXxX"
|
||||
/>
|
||||
</b-form-group>
|
||||
<div class="d-flex justify-content-around">
|
||||
<b-button type="submit" variant="primary">
|
||||
{{ $t('password.change') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</b-form>
|
||||
</b-card>
|
||||
</b-row>
|
||||
<hr>
|
||||
<h2 id="delete-account">
|
||||
{{ $t('account.delete.delete') }}
|
||||
</h2>
|
||||
<b-row>
|
||||
<b-card class="offset-md-2 col-8">
|
||||
<p>
|
||||
{{ $t('account.delete.confirm') }}
|
||||
</p>
|
||||
<b-button type="button" variant="danger" @click="askAccountDeletion">
|
||||
{{ $t('account.delete.delete') }}
|
||||
</b-button>
|
||||
<p class="mt-2 text-muted" style="line-height: 1.1">
|
||||
<small>
|
||||
{{ $t('account.delete.consequence') }}
|
||||
</small>
|
||||
</p>
|
||||
</b-card>
|
||||
</b-row>
|
||||
</div>
|
||||
<div v-else class="m-5 alert alert-secondary">
|
||||
{{ $t('errors.account-no-auth') }}
|
||||
</div>
|
||||
<b-modal id="delete-account-modal" :title="$t('account.delete.delete')" ok-variant="danger" :ok-title="$t('account.delete.delete')" cancel-variant="primary" @ok="deleteMyAccount">
|
||||
<p>
|
||||
{{ $t('account.delete.confirm-twice') }}
|
||||
|
|
18
main.go
18
main.go
|
@ -48,6 +48,7 @@ import (
|
|||
"git.happydns.org/happydns/admin"
|
||||
"git.happydns.org/happydns/api"
|
||||
"git.happydns.org/happydns/config"
|
||||
"git.happydns.org/happydns/model"
|
||||
"git.happydns.org/happydns/storage"
|
||||
|
||||
_ "git.happydns.org/happydns/sources/alwaysdata"
|
||||
|
@ -126,6 +127,23 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
if opts.NoAuth {
|
||||
// Check if the default account exists.
|
||||
if !storage.MainStore.UserExists(api.NO_AUTH_ACCOUNT) {
|
||||
if user, err := happydns.NewUser(api.NO_AUTH_ACCOUNT, ""); err != nil {
|
||||
log.Fatal("Unable to create default account:", err)
|
||||
} else {
|
||||
user.Settings = *happydns.DefaultUserSettings()
|
||||
if err := storage.MainStore.CreateUser(user); err != nil {
|
||||
log.Fatal("Unable to create default account in database:", err)
|
||||
} else {
|
||||
log.Println("Default account for NoAuth created.")
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println("WARNING: NoAuth option has to be use for testing or personnal purpose behind another restriction/authentication method.")
|
||||
}
|
||||
|
||||
log.Println("Do database migrations...")
|
||||
if err = storage.MainStore.DoMigration(); err != nil {
|
||||
log.Fatal("Cannot migrate database: ", err)
|
||||
|
|
|
@ -80,7 +80,9 @@ func NewUser(email string, password string) (u *User, err error) {
|
|||
RegistrationTime: &t,
|
||||
}
|
||||
|
||||
err = u.DefinePassword(password)
|
||||
if len(password) != 0 {
|
||||
err = u.DefinePassword(password)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -118,6 +120,10 @@ func (u *User) DefinePassword(password string) (err error) {
|
|||
|
||||
// CheckAuth compares the given password to the hashed one in the User struct.
|
||||
func (u *User) CheckAuth(password string) bool {
|
||||
if len(password) < 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
return bcrypt.CompareHashAndPassword(u.Password, []byte(password)) == nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue