1
0
mirror of https://github.com/greenpau/caddy-security.git synced 2025-04-18 08:04:02 +03:00

create user registration configuration section

This commit is contained in:
Paul Greenberg 2022-04-04 09:53:07 -04:00
parent 059a464f90
commit a0004fe719
13 changed files with 231 additions and 137 deletions

View File

@ -16,8 +16,8 @@ all: info
@mkdir -p ../xcaddy-$(PLUGIN_NAME) && cd ../xcaddy-$(PLUGIN_NAME) && \
xcaddy build $(CADDY_VERSION) --output ../$(PLUGIN_NAME)/bin/caddy \
--with github.com/greenpau/caddy-security@$(LATEST_GIT_COMMIT)=$(BUILD_DIR) \
--with github.com/greenpau/caddy-trace@v1.1.8
@#--with github.com/greenpau/go-authcrunch@v1.0.22=/home/greenpau/dev/go/src/github.com/greenpau/go-authcrunch
--with github.com/greenpau/caddy-trace@v1.1.8 \
--with github.com/greenpau/go-authcrunch@v1.0.22=/home/greenpau/dev/go/src/github.com/greenpau/go-authcrunch
@#bin/caddy run -config assets/config/Caddyfile
@for f in `find ./assets -type f -name 'Caddyfile'`; do bin/caddy fmt -overwrite $$f; done

View File

@ -23,6 +23,17 @@
path assets/config/users.json
}
user registration localdbRegistry {
dropbox assets/config/registrations.json
title "User Registration"
code "NY2020"
require accept terms
require domain mx
email provider localhost-smtp-server
admin email admin@localhost
identity store localdb
}
authentication portal myportal {
crypto default token lifetime 3600
crypto key sign-verify 01ee2688-36e4-47f9-8c06-d18483702520
@ -38,15 +49,6 @@
action add role authp/user
ui link "Portal Settings" /auth/settings icon "las la-cog"
}
registration {
dropbox assets/config/registrations.json
title "User Registration"
code "NY2020"
require accept terms
require domain mx
email provider localhost-smtp-server
admin email root@localhost
}
}
authorization policy mypolicy {

View File

@ -66,6 +66,10 @@ func parseCaddyfile(d *caddyfile.Dispenser, _ interface{}) (interface{}, error)
if err := parseCaddyfileIdentity(d, repl, app.Config, tld); err != nil {
return nil, err
}
case "user":
if err := parseCaddyfileUser(d, repl, app.Config); err != nil {
return nil, err
}
case "authentication":
if err := parseCaddyfileAuthentication(d, repl, app.Config); err != nil {
return nil, err

View File

@ -22,7 +22,6 @@ import (
"github.com/greenpau/go-authcrunch"
"github.com/greenpau/go-authcrunch/pkg/authn"
"github.com/greenpau/go-authcrunch/pkg/authn/cookie"
"github.com/greenpau/go-authcrunch/pkg/authn/registration"
"github.com/greenpau/go-authcrunch/pkg/authn/ui"
"github.com/greenpau/go-authcrunch/pkg/authz/options"
"github.com/greenpau/go-authcrunch/pkg/errors"
@ -58,22 +57,13 @@ const (
// cookie samesite <lax|strict|none>
// cookie insecure <on|off>
//
// registration {
// disabled <on|off>
// title "User Registration"
// code "NY2020"
// dropbox <file/path/to/registration/dir/>
// require accept terms
// require domain mx
// admin email <email_address> [<email_address_N>]
// }
//
// validate source address
//
// enable source ip tracking
// enable admin api
// enable identity store <name>
// enable identity provider <name>
// enable user registration <name>
// }
//
func parseCaddyfileAuthentication(d *caddyfile.Dispenser, repl *caddy.Replacer, cfg *authcrunch.Config) error {
@ -91,10 +81,9 @@ func parseCaddyfileAuthentication(d *caddyfile.Dispenser, repl *caddy.Replacer,
UI: &ui.Parameters{
Templates: make(map[string]string),
},
UserRegistrationConfig: &registration.Config{},
CookieConfig: &cookie.Config{},
TokenValidatorOptions: &options.TokenValidatorOptions{},
TokenGrantorOptions: &options.TokenGrantorOptions{},
CookieConfig: &cookie.Config{},
TokenValidatorOptions: &options.TokenValidatorOptions{},
TokenGrantorOptions: &options.TokenGrantorOptions{},
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
k := d.Val()
@ -119,10 +108,6 @@ func parseCaddyfileAuthentication(d *caddyfile.Dispenser, repl *caddy.Replacer,
if err := parseCaddyfileAuthPortalTransform(d, repl, p, rootDirective, v); err != nil {
return err
}
case "registration":
if err := parseCaddyfileAuthPortalRegistration(d, repl, p, rootDirective); err != nil {
return err
}
case "enable", "validate":
if err := parseCaddyfileAuthPortalMisc(d, repl, p, rootDirective, k, v); err != nil {
return err

View File

@ -1,88 +0,0 @@
// Copyright 2022 Paul Greenberg greenpau@outlook.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/greenpau/caddy-security/pkg/util"
"github.com/greenpau/go-authcrunch/pkg/authn"
"strings"
)
func parseCaddyfileAuthPortalRegistration(h *caddyfile.Dispenser, repl *caddy.Replacer, portal *authn.PortalConfig, rootDirective string) error {
for nesting := h.Nesting(); h.NextBlock(nesting); {
subDirective := h.Val()
switch subDirective {
case "title":
if !h.NextArg() {
return h.Errf("%s %s subdirective has no value", rootDirective, subDirective)
}
portal.UserRegistrationConfig.Title = util.FindReplace(repl, h.Val())
case "disabled":
if !h.NextArg() {
return h.Errf("%s %s subdirective has no value", rootDirective, subDirective)
}
if h.Val() == "yes" || h.Val() == "on" {
portal.UserRegistrationConfig.Disabled = true
}
case "code":
if !h.NextArg() {
return h.Errf("%s %s subdirective has no value", rootDirective, subDirective)
}
portal.UserRegistrationConfig.Code = util.FindReplace(repl, h.Val())
case "dropbox":
if !h.NextArg() {
return h.Errf("%s %s subdirective has no value", rootDirective, subDirective)
}
portal.UserRegistrationConfig.Dropbox = util.FindReplace(repl, h.Val())
case "require":
args := strings.Join(h.RemainingArgs(), " ")
args = strings.TrimSpace(args)
switch args {
case "accept terms":
portal.UserRegistrationConfig.RequireAcceptTerms = true
case "domain mx":
portal.UserRegistrationConfig.RequireDomainMailRecord = true
case "":
return h.Errf("%s directive has no value", rootDirective)
default:
return h.Errf("%s directive %q is unsupported", rootDirective, args)
}
case "email":
args := util.FindReplaceAll(repl, h.RemainingArgs())
if len(args) != 2 {
return h.Errf("%s directive %q invalid number of args", rootDirective, args)
}
if args[0] != "provider" {
return h.Errf("%s directive must be followed by provider keyword", rootDirective)
}
portal.UserRegistrationConfig.EmailProvider = args[1]
case "admin":
args := util.FindReplaceAll(repl, h.RemainingArgs())
if len(args) < 2 {
return h.Errf("%s directive %q invalid number of args", rootDirective, args)
}
if args[0] != "email" {
return h.Errf("%s directive must be followed by email keyword", rootDirective)
}
portal.UserRegistrationConfig.AdminEmails = args[1:]
default:
return h.Errf("unsupported subdirective for %s: %s", rootDirective, subDirective)
}
}
return nil
}

View File

@ -51,13 +51,6 @@ func TestParseCaddyfileAuthentication(t *testing.T) {
match origin local
action add role authp/user
ui link "Portal Settings" /auth/settings icon "las la-cog"
}
registration {
title "User Registration"
code "NY2020"
dropbox assets/config/registrations.json
require accept terms
require domain mx
}
enable source ip tracking
validate source address
@ -158,13 +151,6 @@ func TestParseCaddyfileAuthentication(t *testing.T) {
}
]
},
"user_registration_config": {
"title": "User Registration",
"code": "NY2020",
"dropbox": "assets/config/registrations.json",
"require_accept_terms": true,
"require_domain_mx": true
},
"user_transformer_configs": [
{
"matchers": [

View File

@ -57,7 +57,6 @@ func TestParseCaddyfileIdentityProvider(t *testing.T) {
{
"name": "myportal",
"ui": {},
"user_registration_config": {},
"cookie_config": {},
"identity_providers": [
"authp"

View File

@ -47,7 +47,6 @@ func TestParseCaddyfileIdentityStore(t *testing.T) {
{
"name": "myportal",
"ui": {},
"user_registration_config": {},
"cookie_config": {},
"identity_stores": [
"localdb"
@ -105,7 +104,6 @@ func TestParseCaddyfileIdentityStore(t *testing.T) {
{
"name": "myportal",
"ui": {},
"user_registration_config": {},
"cookie_config": {},
"identity_stores": [
"contoso.com"

View File

@ -68,7 +68,6 @@ func TestParseCaddyfileIdentity(t *testing.T) {
{
"name": "myportal",
"ui": {},
"user_registration_config": {},
"cookie_config": {},
"identity_stores": [
"localdb"

View File

@ -44,6 +44,10 @@ const (
// bcc <email_address_1> <email_address2>
// }
//
// messaging file provider <name> {
// rootdir <path>
// }
//
func parseCaddyfileMessaging(d *caddyfile.Dispenser, repl *caddy.Replacer, cfg *authcrunch.Config) error {
args := util.FindReplaceAll(repl, d.RemainingArgs())
if len(args) != 3 {
@ -55,7 +59,9 @@ func parseCaddyfileMessaging(d *caddyfile.Dispenser, repl *caddy.Replacer, cfg *
switch args[0] {
case "email":
c := &messaging.EmailProvider{Name: args[2]}
c := &messaging.EmailProvider{
Name: args[2],
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
k := d.Val()
v := util.FindReplaceAll(repl, d.RemainingArgs())
@ -92,6 +98,26 @@ func parseCaddyfileMessaging(d *caddyfile.Dispenser, repl *caddy.Replacer, cfg *
if err := cfg.AddMessagingProvider(c); err != nil {
return errors.ErrMalformedDirective.WithArgs([]string{msgPrefix, args[0], args[1]}, err)
}
case "file":
p := &messaging.FileProvider{
Name: args[2],
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
k := d.Val()
v := util.FindReplaceAll(repl, d.RemainingArgs())
if len(v) != 1 {
return errors.ErrMalformedDirective.WithArgs([]string{msgPrefix, args[0], k}, v)
}
switch k {
case "rootdir":
p.RootDir = v[0]
default:
return errors.ErrMalformedDirective.WithArgs([]string{msgPrefix, args[0], k}, v)
}
}
if err := cfg.AddMessagingProvider(p); err != nil {
return errors.ErrMalformedDirective.WithArgs([]string{msgPrefix, args[0], args[1]}, err)
}
default:
return errors.ErrMalformedDirective.WithArgs(msgPrefix, args)
}

43
caddyfile_user.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2022 Paul Greenberg greenpau@outlook.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/greenpau/caddy-security/pkg/util"
"github.com/greenpau/go-authcrunch"
"github.com/greenpau/go-authcrunch/pkg/errors"
)
const (
userPrefix = "security.user"
)
func parseCaddyfileUser(d *caddyfile.Dispenser, repl *caddy.Replacer, cfg *authcrunch.Config) error {
args := util.FindReplaceAll(repl, d.RemainingArgs())
if len(args) < 2 {
return d.ArgErr()
}
switch {
case args[0] == "registration":
if err := parseCaddyfileUserRegistration(d, repl, cfg, args[1]); err != nil {
return err
}
default:
return errors.ErrMalformedDirective.WithArgs(userPrefix, args)
}
return nil
}

View File

@ -0,0 +1,138 @@
// Copyright 2022 Paul Greenberg greenpau@outlook.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/greenpau/caddy-security/pkg/util"
"github.com/greenpau/go-authcrunch"
"github.com/greenpau/go-authcrunch/pkg/errors"
"github.com/greenpau/go-authcrunch/pkg/registry"
"strings"
)
// parseCaddyfileIdentityProvider parses identity provider configuration.
//
// Syntax:
//
// user registration <name> {
// title <name>
// code <name>
// dropbox <path>
// require accept terms
// require domain mx
// email provider <name>
// admin email <email_address_1> <<email_address_N>
// identity store <name>
// link terms <url>
// link privacy <url>
// }
//
func parseCaddyfileUserRegistration(d *caddyfile.Dispenser, repl *caddy.Replacer, cfg *authcrunch.Config, name string) error {
var disabled bool
r := &registry.UserRegistryConfig{
Name: name,
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
k := d.Val()
args := util.FindReplaceAll(repl, d.RemainingArgs())
rd := mkcp("security.user.registration["+name+"]", k)
switch k {
case "disabled":
disabled = true
case "title":
if len(args) != 1 {
return d.Errf("%s directive %q must contain single value", rd, args)
}
r.Title = args[0]
case "code":
if len(args) != 1 {
return d.Errf("%s directive %q must contain single value", rd, args)
}
r.Code = args[0]
case "dropbox":
if len(args) != 1 {
return d.Errf("%s directive %q must contain single value", rd, args)
}
r.Dropbox = args[0]
case "require":
switch strings.Join(args, " ") {
case "accept terms":
r.RequireAcceptTerms = true
case "domain mx":
r.RequireDomainMailRecord = true
case "":
return d.Errf("%s directive has no value", rd)
default:
return d.Errf("%s directive %q is unsupported", rd, args)
}
case "link":
if len(args) != 2 {
return d.Errf("%s directive %q must contain key-value pair", rd, args)
}
switch args[0] {
case "terms":
r.TermsConditionsLink = args[1]
case "privacy":
r.PrivacyPolicyLink = args[1]
default:
return d.Errf("%s directive %q contains unsupported value", rd, args)
}
case "email":
if len(args) != 2 {
return d.Errf("%s directive %q must contain key-value pair", rd, args)
}
switch args[0] {
case "provider":
r.EmailProvider = args[1]
default:
return d.Errf("%s directive %q contains unsupported value", rd, args)
}
case "identity":
if len(args) != 2 {
return d.Errf("%s directive %q must contain key-value pair", rd, args)
}
switch args[0] {
case "store":
r.IdentityStore = args[1]
default:
return d.Errf("%s directive %q contains unsupported value", rd, args)
}
case "admin":
if len(args) < 2 {
return d.Errf("%s directive %q must contain key-value pair", rd, args)
}
switch args[0] {
case "email", "emails":
r.AdminEmails = args[1:]
default:
return d.Errf("%s directive %q contains unsupported value", rd, args)
}
default:
return errors.ErrMalformedDirective.WithArgs(rd, args)
}
}
if !disabled {
if err := cfg.AddUserRegistry(r); err != nil {
return err
}
}
return nil
}

2
go.mod
View File

@ -9,3 +9,5 @@ require (
github.com/greenpau/go-authcrunch v1.0.22
go.uber.org/zap v1.20.0
)
replace github.com/greenpau/go-authcrunch v1.0.22 => /home/greenpau/dev/go/src/github.com/greenpau/go-authcrunch