diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml
index 4a73009..832abb9 100644
--- a/.github/workflows/contributors.yml
+++ b/.github/workflows/contributors.yml
@@ -10,6 +10,13 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
+      - name: Delete upstream contributor branch
+        # Allow continue on failure to account for when the
+        # upstream branch is deleted or does not exist.
+        continue-on-error: true
+        run: git push origin --delete update-contributors
+      - name: Create up-to-date contributors branch
+        run: git checkout -B update-contributors
       - uses: BobAnkh/add-contributors@v0.2.2
         with:
           CONTRIBUTOR: "## Contributors"
diff --git a/.golangci.yaml b/.golangci.yaml
index 153cd7c..56fdc28 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -29,6 +29,7 @@ linters:
     - wrapcheck
     - dupl
     - makezero
+    - maintidx
 
     # We might want to enable this, but it might be a lot of work
     - cyclop
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d44db56..4445444 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,27 +1,39 @@
 # CHANGELOG
 
-**0.15.0 (2022-xx-xx):**
+## 0.15.0 (2022-xx-xx)
 
-**BREAKING**:
+**Note:** Take a backup of your database before upgrading.
+
+### BREAKING
 
 - Boundaries between Namespaces has been removed and all nodes can communicate by default [#357](https://github.com/juanfont/headscale/pull/357)
   - To limit access between nodes, use [ACLs](./docs/acls.md).
 
-**Changes**:
+### Features
+
+- Add support for writing ACL files with YAML [#359](https://github.com/juanfont/headscale/pull/359)
+- Users can now use emails in ACL's groups [#372](https://github.com/juanfont/headscale/issues/372)
+- Add shorthand aliases for commands and subcommands [#376](https://github.com/juanfont/headscale/pull/376)
+
+### Changes
 
 - Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346)
+- Simplify the code behind registration of machines [#366](https://github.com/juanfont/headscale/pull/366)
+  - Nodes are now only written to database if they are registrated successfully
+- Fix a limitation in the ACLs that prevented users to write rules with `*` as source [#374](https://github.com/juanfont/headscale/issues/374)
+- Reduce the overhead of marshal/unmarshal for Hostinfo, routes and endpoints by using specific types in Machine [#371](https://github.com/juanfont/headscale/pull/371)
 
-**0.14.0 (2022-02-24):**
+## 0.14.0 (2022-02-24)
 
-**UPCOMING BREAKING**:
-From the **next** version (`0.15.0`), all machines will be able to communicate regardless of
+**UPCOMING ### BREAKING
+From the **next\*\* version (`0.15.0`), all machines will be able to communicate regardless of
 if they are in the same namespace. This means that the behaviour currently limited to ACLs
 will become default. From version `0.15.0`, all limitation of communications must be done
 with ACLs.
 
 This is a part of aligning `headscale`'s behaviour with Tailscale's upstream behaviour.
 
-**BREAKING**:
+### BREAKING
 
 - ACLs have been rewritten to align with the bevaviour Tailscale Control Panel provides. **NOTE:** This is only active if you use ACLs
   - Namespaces are now treated as Users
@@ -29,17 +41,17 @@ This is a part of aligning `headscale`'s behaviour with Tailscale's upstream beh
   - Tags should now work correctly and adding a host to Headscale should now reload the rules.
   - The documentation have a [fictional example](docs/acls.md) that should cover some use cases of the ACLs features
 
-**Features**:
+### Features
 
 - Add support for configurable mTLS [docs](docs/tls.md#configuring-mutual-tls-authentication-mtls) [#297](https://github.com/juanfont/headscale/pull/297)
 
-**Changes**:
+### Changes
 
 - Remove dependency on CGO (switch from CGO SQLite to pure Go) [#346](https://github.com/juanfont/headscale/pull/346)
 
 **0.13.0 (2022-02-18):**
 
-**Features**:
+### Features
 
 - Add IPv6 support to the prefix assigned to namespaces
 - Add API Key support
@@ -50,7 +62,7 @@ This is a part of aligning `headscale`'s behaviour with Tailscale's upstream beh
   - `oidc.domain_map` option has been removed
   - `strip_email_domain` option has been added (see [config-example.yaml](./config_example.yaml))
 
-**Changes**:
+### Changes
 
 - `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208)
 - Upgrade `tailscale` (1.20.4) and other dependencies to latest [#314](https://github.com/juanfont/headscale/pull/314)
@@ -59,35 +71,35 @@ This is a part of aligning `headscale`'s behaviour with Tailscale's upstream beh
 
 **0.12.4 (2022-01-29):**
 
-**Changes**:
+### Changes
 
 - Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)
 - Trim whitespace before reading Private Key from file [#289](https://github.com/juanfont/headscale/pull/289)
 - Add new command to generate a private key for `headscale` [#290](https://github.com/juanfont/headscale/pull/290)
 - Fixed issue where hosts deleted from control server may be written back to the database, as long as they are connected to the control server [#278](https://github.com/juanfont/headscale/pull/278)
 
-**0.12.3 (2022-01-13):**
+## 0.12.3 (2022-01-13)
 
-**Changes**:
+### Changes
 
 - Added Alpine container [#270](https://github.com/juanfont/headscale/pull/270)
 - Minor updates in dependencies [#271](https://github.com/juanfont/headscale/pull/271)
 
-**0.12.2 (2022-01-11):**
+## 0.12.2 (2022-01-11)
 
 Happy New Year!
 
-**Changes**:
+### Changes
 
 - Fix Docker release [#258](https://github.com/juanfont/headscale/pull/258)
 - Rewrite main docs [#262](https://github.com/juanfont/headscale/pull/262)
 - Improve Docker docs [#263](https://github.com/juanfont/headscale/pull/263)
 
-**0.12.1 (2021-12-24):**
+## 0.12.1 (2021-12-24)
 
 (We are skipping 0.12.0 to correct a mishap done weeks ago with the version tagging)
 
-**BREAKING**:
+### BREAKING
 
 - Upgrade to Tailscale 1.18 [#229](https://github.com/juanfont/headscale/pull/229)
   - This change requires a new format for private key, private keys are now generated automatically:
@@ -95,19 +107,19 @@ Happy New Year!
     2. Restart `headscale`, a new key will be generated.
     3. Restart all Tailscale clients to fetch the new key
 
-**Changes**:
+### Changes
 
 - Unify configuration example [#197](https://github.com/juanfont/headscale/pull/197)
 - Add stricter linting and formatting [#223](https://github.com/juanfont/headscale/pull/223)
 
-**Features**:
+### Features
 
 - Add gRPC and HTTP API (HTTP API is currently disabled) [#204](https://github.com/juanfont/headscale/pull/204)
 - Use gRPC between the CLI and the server [#206](https://github.com/juanfont/headscale/pull/206), [#212](https://github.com/juanfont/headscale/pull/212)
 - Beta OpenID Connect support [#126](https://github.com/juanfont/headscale/pull/126), [#227](https://github.com/juanfont/headscale/pull/227)
 
-**0.11.0 (2021-10-25):**
+## 0.11.0 (2021-10-25)
 
-**BREAKING**:
+### BREAKING
 
 - Make headscale fetch DERP map from URL and file [#196](https://github.com/juanfont/headscale/pull/196)
diff --git a/acls.go b/acls.go
index ce14a89..84063a1 100644
--- a/acls.go
+++ b/acls.go
@@ -5,11 +5,13 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"path/filepath"
 	"strconv"
 	"strings"
 
 	"github.com/rs/zerolog/log"
 	"github.com/tailscale/hujson"
+	"gopkg.in/yaml.v3"
 	"inet.af/netaddr"
 	"tailscale.com/tailcfg"
 )
@@ -53,16 +55,36 @@ func (h *Headscale) LoadACLPolicy(path string) error {
 		return err
 	}
 
-	ast, err := hujson.Parse(policyBytes)
-	if err != nil {
-		return err
-	}
-	ast.Standardize()
-	policyBytes = ast.Pack()
-	err = json.Unmarshal(policyBytes, &policy)
-	if err != nil {
-		return err
+	switch filepath.Ext(path) {
+	case ".yml", ".yaml":
+		log.Debug().
+			Str("path", path).
+			Bytes("file", policyBytes).
+			Msg("Loading ACLs from YAML")
+
+		err := yaml.Unmarshal(policyBytes, &policy)
+		if err != nil {
+			return err
+		}
+
+		log.Trace().
+			Interface("policy", policy).
+			Msg("Loaded policy from YAML")
+
+	default:
+		ast, err := hujson.Parse(policyBytes)
+		if err != nil {
+			return err
+		}
+
+		ast.Standardize()
+		policyBytes = ast.Pack()
+		err = json.Unmarshal(policyBytes, &policy)
+		if err != nil {
+			return err
+		}
 	}
+
 	if policy.IsZero() {
 		return errEmptyPolicy
 	}
@@ -138,7 +160,7 @@ func (h *Headscale) generateACLPolicySrcIP(
 	aclPolicy ACLPolicy,
 	u string,
 ) ([]string, error) {
-	return expandAlias(machines, aclPolicy, u)
+	return expandAlias(machines, aclPolicy, u, h.cfg.OIDC.StripEmaildomain)
 }
 
 func (h *Headscale) generateACLPolicyDestPorts(
@@ -164,7 +186,12 @@ func (h *Headscale) generateACLPolicyDestPorts(
 		alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
 	}
 
-	expanded, err := expandAlias(machines, aclPolicy, alias)
+	expanded, err := expandAlias(
+		machines,
+		aclPolicy,
+		alias,
+		h.cfg.OIDC.StripEmaildomain,
+	)
 	if err != nil {
 		return nil, err
 	}
@@ -196,6 +223,7 @@ func expandAlias(
 	machines []Machine,
 	aclPolicy ACLPolicy,
 	alias string,
+	stripEmailDomain bool,
 ) ([]string, error) {
 	ips := []string{}
 	if alias == "*" {
@@ -203,7 +231,7 @@ func expandAlias(
 	}
 
 	if strings.HasPrefix(alias, "group:") {
-		namespaces, err := expandGroup(aclPolicy, alias)
+		namespaces, err := expandGroup(aclPolicy, alias, stripEmailDomain)
 		if err != nil {
 			return ips, err
 		}
@@ -218,20 +246,14 @@ func expandAlias(
 	}
 
 	if strings.HasPrefix(alias, "tag:") {
-		owners, err := expandTagOwners(aclPolicy, alias)
+		owners, err := expandTagOwners(aclPolicy, alias, stripEmailDomain)
 		if err != nil {
 			return ips, err
 		}
 		for _, namespace := range owners {
 			machines := filterMachinesByNamespace(machines, namespace)
 			for _, machine := range machines {
-				if len(machine.HostInfo) == 0 {
-					continue
-				}
-				hi, err := machine.GetHostInfo()
-				if err != nil {
-					return ips, err
-				}
+				hi := machine.GetHostInfo()
 				for _, t := range hi.RequestTags {
 					if alias == t {
 						ips = append(ips, machine.IPAddresses.ToStringSlice()...)
@@ -245,10 +267,8 @@ func expandAlias(
 
 	// if alias is a namespace
 	nodes := filterMachinesByNamespace(machines, alias)
-	nodes, err := excludeCorrectlyTaggedNodes(aclPolicy, nodes, alias)
-	if err != nil {
-		return ips, err
-	}
+	nodes = excludeCorrectlyTaggedNodes(aclPolicy, nodes, alias)
+
 	for _, n := range nodes {
 		ips = append(ips, n.IPAddresses.ToStringSlice()...)
 	}
@@ -283,7 +303,7 @@ func excludeCorrectlyTaggedNodes(
 	aclPolicy ACLPolicy,
 	nodes []Machine,
 	namespace string,
-) ([]Machine, error) {
+) []Machine {
 	out := []Machine{}
 	tags := []string{}
 	for tag, ns := range aclPolicy.TagOwners {
@@ -293,15 +313,8 @@ func excludeCorrectlyTaggedNodes(
 	}
 	// for each machine if tag is in tags list, don't append it.
 	for _, machine := range nodes {
-		if len(machine.HostInfo) == 0 {
-			out = append(out, machine)
+		hi := machine.GetHostInfo()
 
-			continue
-		}
-		hi, err := machine.GetHostInfo()
-		if err != nil {
-			return out, err
-		}
 		found := false
 		for _, t := range hi.RequestTags {
 			if containsString(tags, t) {
@@ -315,7 +328,7 @@ func excludeCorrectlyTaggedNodes(
 		}
 	}
 
-	return out, nil
+	return out
 }
 
 func expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
@@ -374,7 +387,11 @@ func filterMachinesByNamespace(machines []Machine, namespace string) []Machine {
 
 // expandTagOwners will return a list of namespace. An owner can be either a namespace or a group
 // a group cannot be composed of groups.
-func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
+func expandTagOwners(
+	aclPolicy ACLPolicy,
+	tag string,
+	stripEmailDomain bool,
+) ([]string, error) {
 	var owners []string
 	ows, ok := aclPolicy.TagOwners[tag]
 	if !ok {
@@ -386,7 +403,7 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
 	}
 	for _, owner := range ows {
 		if strings.HasPrefix(owner, "group:") {
-			gs, err := expandGroup(aclPolicy, owner)
+			gs, err := expandGroup(aclPolicy, owner, stripEmailDomain)
 			if err != nil {
 				return []string{}, err
 			}
@@ -401,8 +418,13 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
 
 // expandGroup will return the list of namespace inside the group
 // after some validation.
-func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
-	groups, ok := aclPolicy.Groups[group]
+func expandGroup(
+	aclPolicy ACLPolicy,
+	group string,
+	stripEmailDomain bool,
+) ([]string, error) {
+	outGroups := []string{}
+	aclGroups, ok := aclPolicy.Groups[group]
 	if !ok {
 		return []string{}, fmt.Errorf(
 			"group %v isn't registered. %w",
@@ -410,14 +432,23 @@ func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
 			errInvalidGroup,
 		)
 	}
-	for _, g := range groups {
-		if strings.HasPrefix(g, "group:") {
+	for _, group := range aclGroups {
+		if strings.HasPrefix(group, "group:") {
 			return []string{}, fmt.Errorf(
 				"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
 				errInvalidGroup,
 			)
 		}
+		grp, err := NormalizeNamespaceName(group, stripEmailDomain)
+		if err != nil {
+			return []string{}, fmt.Errorf(
+				"failed to normalize group %q, err: %w",
+				group,
+				errInvalidGroup,
+			)
+		}
+		outGroups = append(outGroups, grp)
 	}
 
-	return groups, nil
+	return outGroups, nil
 }
diff --git a/acls_test.go b/acls_test.go
index 5534257..9dcc40b 100644
--- a/acls_test.go
+++ b/acls_test.go
@@ -6,7 +6,6 @@ import (
 	"testing"
 
 	"gopkg.in/check.v1"
-	"gorm.io/datatypes"
 	"inet.af/netaddr"
 	"tailscale.com/tailcfg"
 )
@@ -108,9 +107,12 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) {
 
 	_, err = app.GetMachine("user1", "testmachine")
 	c.Assert(err, check.NotNil)
-	hostInfo := []byte(
-		"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}",
-	)
+	hostInfo := tailcfg.Hostinfo{
+		OS:          "centos",
+		Hostname:    "testmachine",
+		RequestTags: []string{"tag:test"},
+	}
+
 	machine := Machine{
 		ID:             0,
 		MachineKey:     "foo",
@@ -119,10 +121,9 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) {
 		Name:           "testmachine",
 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
-		HostInfo:       datatypes.JSON(hostInfo),
+		HostInfo:       HostInfo(hostInfo),
 	}
 	app.db.Save(&machine)
 
@@ -152,9 +153,12 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) {
 
 	_, err = app.GetMachine("user1", "testmachine")
 	c.Assert(err, check.NotNil)
-	hostInfo := []byte(
-		"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}",
-	)
+	hostInfo := tailcfg.Hostinfo{
+		OS:          "centos",
+		Hostname:    "testmachine",
+		RequestTags: []string{"tag:test"},
+	}
+
 	machine := Machine{
 		ID:             1,
 		MachineKey:     "12345",
@@ -163,10 +167,9 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) {
 		Name:           "testmachine",
 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
-		HostInfo:       datatypes.JSON(hostInfo),
+		HostInfo:       HostInfo(hostInfo),
 	}
 	app.db.Save(&machine)
 
@@ -196,9 +199,12 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
 
 	_, err = app.GetMachine("user1", "testmachine")
 	c.Assert(err, check.NotNil)
-	hostInfo := []byte(
-		"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:foo\"]}",
-	)
+	hostInfo := tailcfg.Hostinfo{
+		OS:          "centos",
+		Hostname:    "testmachine",
+		RequestTags: []string{"tag:foo"},
+	}
+
 	machine := Machine{
 		ID:             1,
 		MachineKey:     "12345",
@@ -207,10 +213,9 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
 		Name:           "testmachine",
 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
-		HostInfo:       datatypes.JSON(hostInfo),
+		HostInfo:       HostInfo(hostInfo),
 	}
 	app.db.Save(&machine)
 
@@ -239,9 +244,12 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
 
 	_, err = app.GetMachine("user1", "webserver")
 	c.Assert(err, check.NotNil)
-	hostInfo := []byte(
-		"{\"OS\":\"centos\",\"Hostname\":\"webserver\",\"RequestTags\":[\"tag:webapp\"]}",
-	)
+	hostInfo := tailcfg.Hostinfo{
+		OS:          "centos",
+		Hostname:    "webserver",
+		RequestTags: []string{"tag:webapp"},
+	}
+
 	machine := Machine{
 		ID:             1,
 		MachineKey:     "12345",
@@ -250,14 +258,16 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
 		Name:           "webserver",
 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
-		HostInfo:       datatypes.JSON(hostInfo),
+		HostInfo:       HostInfo(hostInfo),
 	}
 	app.db.Save(&machine)
 	_, err = app.GetMachine("user1", "user")
-	hostInfo = []byte("{\"OS\":\"debian\",\"Hostname\":\"user\"}")
+	hostInfo2 := tailcfg.Hostinfo{
+		OS:       "debian",
+		Hostname: "Hostname",
+	}
 	c.Assert(err, check.NotNil)
 	machine = Machine{
 		ID:             2,
@@ -267,10 +277,9 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
 		Name:           "user",
 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
-		HostInfo:       datatypes.JSON(hostInfo),
+		HostInfo:       HostInfo(hostInfo2),
 	}
 	app.db.Save(&machine)
 
@@ -328,6 +337,22 @@ func (s *Suite) TestPortWildcard(c *check.C) {
 	c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
 }
 
+func (s *Suite) TestPortWildcardYAML(c *check.C) {
+	err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.yaml")
+	c.Assert(err, check.IsNil)
+
+	rules, err := app.generateACLRules()
+	c.Assert(err, check.IsNil)
+	c.Assert(rules, check.NotNil)
+
+	c.Assert(rules, check.HasLen, 1)
+	c.Assert(rules[0].DstPorts, check.HasLen, 1)
+	c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
+	c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
+	c.Assert(rules[0].SrcIPs, check.HasLen, 1)
+	c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
+}
+
 func (s *Suite) TestPortNamespace(c *check.C) {
 	namespace, err := app.CreateNamespace("testnamespace")
 	c.Assert(err, check.IsNil)
@@ -345,7 +370,6 @@ func (s *Suite) TestPortNamespace(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    ips,
 		AuthKeyID:      uint(pak.ID),
@@ -388,7 +412,6 @@ func (s *Suite) TestPortGroup(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    ips,
 		AuthKeyID:      uint(pak.ID),
@@ -414,8 +437,9 @@ func (s *Suite) TestPortGroup(c *check.C) {
 
 func Test_expandGroup(t *testing.T) {
 	type args struct {
-		aclPolicy ACLPolicy
-		group     string
+		aclPolicy        ACLPolicy
+		group            string
+		stripEmailDomain bool
 	}
 	tests := []struct {
 		name    string
@@ -432,7 +456,8 @@ func Test_expandGroup(t *testing.T) {
 						"group:foo":  []string{"user2", "user3"},
 					},
 				},
-				group: "group:test",
+				group:            "group:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1", "user2", "user3"},
 			wantErr: false,
@@ -446,15 +471,54 @@ func Test_expandGroup(t *testing.T) {
 						"group:foo":  []string{"user2", "user3"},
 					},
 				},
-				group: "group:undefined",
+				group:            "group:undefined",
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
 		},
+		{
+			name: "Expand emails in group",
+			args: args{
+				aclPolicy: ACLPolicy{
+					Groups: Groups{
+						"group:admin": []string{
+							"joe.bar@gmail.com",
+							"john.doe@yahoo.fr",
+						},
+					},
+				},
+				group:            "group:admin",
+				stripEmailDomain: true,
+			},
+			want:    []string{"joe.bar", "john.doe"},
+			wantErr: false,
+		},
+		{
+			name: "Expand emails in group",
+			args: args{
+				aclPolicy: ACLPolicy{
+					Groups: Groups{
+						"group:admin": []string{
+							"joe.bar@gmail.com",
+							"john.doe@yahoo.fr",
+						},
+					},
+				},
+				group:            "group:admin",
+				stripEmailDomain: false,
+			},
+			want:    []string{"joe.bar.gmail.com", "john.doe.yahoo.fr"},
+			wantErr: false,
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			got, err := expandGroup(test.args.aclPolicy, test.args.group)
+			got, err := expandGroup(
+				test.args.aclPolicy,
+				test.args.group,
+				test.args.stripEmailDomain,
+			)
 			if (err != nil) != test.wantErr {
 				t.Errorf("expandGroup() error = %v, wantErr %v", err, test.wantErr)
 
@@ -469,8 +533,9 @@ func Test_expandGroup(t *testing.T) {
 
 func Test_expandTagOwners(t *testing.T) {
 	type args struct {
-		aclPolicy ACLPolicy
-		tag       string
+		aclPolicy        ACLPolicy
+		tag              string
+		stripEmailDomain bool
 	}
 	tests := []struct {
 		name    string
@@ -484,7 +549,8 @@ func Test_expandTagOwners(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:test": []string{"user1"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1"},
 			wantErr: false,
@@ -496,7 +562,8 @@ func Test_expandTagOwners(t *testing.T) {
 					Groups:    Groups{"group:foo": []string{"user1", "user2"}},
 					TagOwners: TagOwners{"tag:test": []string{"group:foo"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1", "user2"},
 			wantErr: false,
@@ -508,7 +575,8 @@ func Test_expandTagOwners(t *testing.T) {
 					Groups:    Groups{"group:foo": []string{"user1", "user2"}},
 					TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{"user1", "user2", "user3"},
 			wantErr: false,
@@ -519,7 +587,8 @@ func Test_expandTagOwners(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -531,7 +600,8 @@ func Test_expandTagOwners(t *testing.T) {
 					Groups:    Groups{"group:bar": []string{"user1", "user2"}},
 					TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}},
 				},
-				tag: "tag:test",
+				tag:              "tag:test",
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -539,7 +609,11 @@ func Test_expandTagOwners(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			got, err := expandTagOwners(test.args.aclPolicy, test.args.tag)
+			got, err := expandTagOwners(
+				test.args.aclPolicy,
+				test.args.tag,
+				test.args.stripEmailDomain,
+			)
 			if (err != nil) != test.wantErr {
 				t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr)
 
@@ -701,9 +775,10 @@ func Test_listMachinesInNamespace(t *testing.T) {
 // nolint
 func Test_expandAlias(t *testing.T) {
 	type args struct {
-		machines  []Machine
-		aclPolicy ACLPolicy
-		alias     string
+		machines         []Machine
+		aclPolicy        ACLPolicy
+		alias            string
+		stripEmailDomain bool
 	}
 	tests := []struct {
 		name    string
@@ -723,7 +798,8 @@ func Test_expandAlias(t *testing.T) {
 						},
 					},
 				},
-				aclPolicy: ACLPolicy{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"*"},
 			wantErr: false,
@@ -761,6 +837,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					Groups: Groups{"group:accountant": []string{"joe", "marc"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
 			wantErr: false,
@@ -798,6 +875,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					Groups: Groups{"group:accountant": []string{"joe", "marc"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -805,9 +883,10 @@ func Test_expandAlias(t *testing.T) {
 		{
 			name: "simple ipaddress",
 			args: args{
-				alias:     "10.0.0.3",
-				machines:  []Machine{},
-				aclPolicy: ACLPolicy{},
+				alias:            "10.0.0.3",
+				machines:         []Machine{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"10.0.0.3"},
 			wantErr: false,
@@ -822,6 +901,7 @@ func Test_expandAlias(t *testing.T) {
 						"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"),
 					},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"192.168.1.0/24"},
 			wantErr: false,
@@ -829,9 +909,10 @@ func Test_expandAlias(t *testing.T) {
 		{
 			name: "simple host",
 			args: args{
-				alias:     "10.0.0.1",
-				machines:  []Machine{},
-				aclPolicy: ACLPolicy{},
+				alias:            "10.0.0.1",
+				machines:         []Machine{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"10.0.0.1"},
 			wantErr: false,
@@ -839,9 +920,10 @@ func Test_expandAlias(t *testing.T) {
 		{
 			name: "simple CIDR",
 			args: args{
-				alias:     "10.0.0.0/16",
-				machines:  []Machine{},
-				aclPolicy: ACLPolicy{},
+				alias:            "10.0.0.0/16",
+				machines:         []Machine{},
+				aclPolicy:        ACLPolicy{},
+				stripEmailDomain: true,
 			},
 			want:    []string{"10.0.0.0/16"},
 			wantErr: false,
@@ -856,18 +938,22 @@ func Test_expandAlias(t *testing.T) {
 							netaddr.MustParseIP("100.64.0.1"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "foo",
+							RequestTags: []string{"tag:hr-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
 							netaddr.MustParseIP("100.64.0.2"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "foo",
+							RequestTags: []string{"tag:hr-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
@@ -885,6 +971,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"100.64.0.1", "100.64.0.2"},
 			wantErr: false,
@@ -925,6 +1012,7 @@ func Test_expandAlias(t *testing.T) {
 						"tag:accountant-webserver": []string{"group:accountant"},
 					},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{},
 			wantErr: true,
@@ -939,18 +1027,22 @@ func Test_expandAlias(t *testing.T) {
 							netaddr.MustParseIP("100.64.0.1"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "foo",
+							RequestTags: []string{"tag:accountant-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
 							netaddr.MustParseIP("100.64.0.2"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "foo",
+							RequestTags: []string{"tag:accountant-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
@@ -968,6 +1060,7 @@ func Test_expandAlias(t *testing.T) {
 				aclPolicy: ACLPolicy{
 					TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
 				},
+				stripEmailDomain: true,
 			},
 			want:    []string{"100.64.0.4"},
 			wantErr: false,
@@ -979,6 +1072,7 @@ func Test_expandAlias(t *testing.T) {
 				test.args.machines,
 				test.args.aclPolicy,
 				test.args.alias,
+				test.args.stripEmailDomain,
 			)
 			if (err != nil) != test.wantErr {
 				t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr)
@@ -1016,18 +1110,22 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
 							netaddr.MustParseIP("100.64.0.1"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "foo",
+							RequestTags: []string{"tag:accountant-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
 							netaddr.MustParseIP("100.64.0.2"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "foo",
+							RequestTags: []string{"tag:accountant-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
@@ -1044,7 +1142,6 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
 					Namespace:   Namespace{Name: "joe"},
 				},
 			},
-			wantErr: false,
 		},
 		{
 			name: "all nodes have invalid tags, don't exclude them",
@@ -1058,18 +1155,22 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
 							netaddr.MustParseIP("100.64.0.1"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "hr-web1",
+							RequestTags: []string{"tag:hr-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
 							netaddr.MustParseIP("100.64.0.2"),
 						},
 						Namespace: Namespace{Name: "joe"},
-						HostInfo: []byte(
-							"{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}",
-						),
+						HostInfo: HostInfo{
+							OS:          "centos",
+							Hostname:    "hr-web2",
+							RequestTags: []string{"tag:hr-webserver"},
+						},
 					},
 					{
 						IPAddresses: MachineAddresses{
@@ -1086,18 +1187,22 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
 						netaddr.MustParseIP("100.64.0.1"),
 					},
 					Namespace: Namespace{Name: "joe"},
-					HostInfo: []byte(
-						"{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}",
-					),
+					HostInfo: HostInfo{
+						OS:          "centos",
+						Hostname:    "hr-web1",
+						RequestTags: []string{"tag:hr-webserver"},
+					},
 				},
 				{
 					IPAddresses: MachineAddresses{
 						netaddr.MustParseIP("100.64.0.2"),
 					},
 					Namespace: Namespace{Name: "joe"},
-					HostInfo: []byte(
-						"{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}",
-					),
+					HostInfo: HostInfo{
+						OS:          "centos",
+						Hostname:    "hr-web2",
+						RequestTags: []string{"tag:hr-webserver"},
+					},
 				},
 				{
 					IPAddresses: MachineAddresses{
@@ -1106,25 +1211,15 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
 					Namespace: Namespace{Name: "joe"},
 				},
 			},
-			wantErr: false,
 		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			got, err := excludeCorrectlyTaggedNodes(
+			got := excludeCorrectlyTaggedNodes(
 				test.args.aclPolicy,
 				test.args.nodes,
 				test.args.namespace,
 			)
-			if (err != nil) != test.wantErr {
-				t.Errorf(
-					"excludeCorrectlyTaggedNodes() error = %v, wantErr %v",
-					err,
-					test.wantErr,
-				)
-
-				return
-			}
 			if !reflect.DeepEqual(got, test.want) {
 				t.Errorf("excludeCorrectlyTaggedNodes() = %v, want %v", got, test.want)
 			}
diff --git a/acls_types.go b/acls_types.go
index 08e650f..fb86982 100644
--- a/acls_types.go
+++ b/acls_types.go
@@ -5,23 +5,24 @@ import (
 	"strings"
 
 	"github.com/tailscale/hujson"
+	"gopkg.in/yaml.v3"
 	"inet.af/netaddr"
 )
 
 // ACLPolicy represents a Tailscale ACL Policy.
 type ACLPolicy struct {
-	Groups    Groups    `json:"Groups"`
-	Hosts     Hosts     `json:"Hosts"`
-	TagOwners TagOwners `json:"TagOwners"`
-	ACLs      []ACL     `json:"ACLs"`
-	Tests     []ACLTest `json:"Tests"`
+	Groups    Groups    `json:"Groups"    yaml:"Groups"`
+	Hosts     Hosts     `json:"Hosts"     yaml:"Hosts"`
+	TagOwners TagOwners `json:"TagOwners" yaml:"TagOwners"`
+	ACLs      []ACL     `json:"ACLs"      yaml:"ACLs"`
+	Tests     []ACLTest `json:"Tests"     yaml:"Tests"`
 }
 
 // ACL is a basic rule for the ACL Policy.
 type ACL struct {
-	Action string   `json:"Action"`
-	Users  []string `json:"Users"`
-	Ports  []string `json:"Ports"`
+	Action string   `json:"Action" yaml:"Action"`
+	Users  []string `json:"Users"  yaml:"Users"`
+	Ports  []string `json:"Ports"  yaml:"Ports"`
 }
 
 // Groups references a series of alias in the ACL rules.
@@ -35,9 +36,9 @@ type TagOwners map[string][]string
 
 // ACLTest is not implemented, but should be use to check if a certain rule is allowed.
 type ACLTest struct {
-	User  string   `json:"User"`
-	Allow []string `json:"Allow"`
-	Deny  []string `json:"Deny,omitempty"`
+	User  string   `json:"User"           yaml:"User"`
+	Allow []string `json:"Allow"          yaml:"Allow"`
+	Deny  []string `json:"Deny,omitempty" yaml:"Deny,omitempty"`
 }
 
 // UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
@@ -69,6 +70,27 @@ func (hosts *Hosts) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+// UnmarshalYAML allows to parse the Hosts directly into netaddr objects.
+func (hosts *Hosts) UnmarshalYAML(data []byte) error {
+	newHosts := Hosts{}
+	hostIPPrefixMap := make(map[string]string)
+
+	err := yaml.Unmarshal(data, &hostIPPrefixMap)
+	if err != nil {
+		return err
+	}
+	for host, prefixStr := range hostIPPrefixMap {
+		prefix, err := netaddr.ParseIPPrefix(prefixStr)
+		if err != nil {
+			return err
+		}
+		newHosts[host] = prefix
+	}
+	*hosts = newHosts
+
+	return nil
+}
+
 // IsZero is perhaps a bit naive here.
 func (policy ACLPolicy) IsZero() bool {
 	if len(policy.Groups) == 0 && len(policy.Hosts) == 0 && len(policy.ACLs) == 0 {
diff --git a/api.go b/api.go
index bb5495a..3b3b675 100644
--- a/api.go
+++ b/api.go
@@ -22,7 +22,7 @@ import (
 
 const (
 	reservedResponseHeaderSize               = 4
-	RegisterMethodAuthKey                    = "authKey"
+	RegisterMethodAuthKey                    = "authkey"
 	RegisterMethodOIDC                       = "oidc"
 	RegisterMethodCLI                        = "cli"
 	ErrRegisterMethodCLIDoesNotSupportExpire = Error(
@@ -125,25 +125,50 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) {
 	machine, err := h.GetMachineByMachineKey(machineKey)
 	if errors.Is(err, gorm.ErrRecordNotFound) {
 		log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
-		newMachine := Machine{
-			Expiry:     &time.Time{},
-			MachineKey: MachinePublicKeyStripPrefix(machineKey),
-			Name:       req.Hostinfo.Hostname,
-		}
-		if err := h.db.Create(&newMachine).Error; err != nil {
-			log.Error().
-				Caller().
-				Err(err).
-				Msg("Could not create row")
-			machineRegistrations.WithLabelValues("unknown", "web", "error", machine.Namespace.Name).
-				Inc()
+
+		machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
+
+		// If the machine has AuthKey set, handle registration via PreAuthKeys
+		if req.Auth.AuthKey != "" {
+			h.handleAuthKey(ctx, machineKey, req)
 
 			return
 		}
-		machine = &newMachine
+
+		// The machine did not have a key to authenticate, which means
+		// that we rely on a method that calls back some how (OpenID or CLI)
+		// We create the machine and then keep it around until a callback
+		// happens
+		newMachine := Machine{
+			MachineKey: machineKeyStr,
+			Name:       req.Hostinfo.Hostname,
+			NodeKey:    NodePublicKeyStripPrefix(req.NodeKey),
+			LastSeen:   &now,
+			Expiry:     &time.Time{},
+		}
+
+		if !req.Expiry.IsZero() {
+			log.Trace().
+				Caller().
+				Str("machine", req.Hostinfo.Hostname).
+				Time("expiry", req.Expiry).
+				Msg("Non-zero expiry time requested")
+			newMachine.Expiry = &req.Expiry
+		}
+
+		h.registrationCache.Set(
+			machineKeyStr,
+			newMachine,
+			registerCacheExpiration,
+		)
+
+		h.handleMachineRegistrationNew(ctx, machineKey, req)
+
+		return
 	}
 
-	if machine.Registered {
+	// The machine is already registered, so we need to pass through reauth or key update.
+	if machine != nil {
 		// If the NodeKey stored in headscale is the same as the key presented in a registration
 		// request, then we have a node that is either:
 		// - Trying to log out (sending a expiry in the past)
@@ -180,15 +205,6 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) {
 
 		return
 	}
-
-	// If the machine has AuthKey set, handle registration via PreAuthKeys
-	if req.Auth.AuthKey != "" {
-		h.handleAuthKey(ctx, machineKey, req, *machine)
-
-		return
-	}
-
-	h.handleMachineRegistrationNew(ctx, machineKey, req, *machine)
 }
 
 func (h *Headscale) getMapResponse(
@@ -402,7 +418,7 @@ func (h *Headscale) handleMachineExpired(
 		Msg("Machine registration has expired. Sending a authurl to register")
 
 	if registerRequest.Auth.AuthKey != "" {
-		h.handleAuthKey(ctx, machineKey, registerRequest, machine)
+		h.handleAuthKey(ctx, machineKey, registerRequest)
 
 		return
 	}
@@ -465,13 +481,12 @@ func (h *Headscale) handleMachineRegistrationNew(
 	ctx *gin.Context,
 	machineKey key.MachinePublic,
 	registerRequest tailcfg.RegisterRequest,
-	machine Machine,
 ) {
 	resp := tailcfg.RegisterResponse{}
 
 	// The machine registration is new, redirect the client to the registration URL
 	log.Debug().
-		Str("machine", machine.Name).
+		Str("machine", registerRequest.Hostinfo.Hostname).
 		Msg("The node is sending us a new NodeKey, sending auth url")
 	if h.cfg.OIDC.Issuer != "" {
 		resp.AuthURL = fmt.Sprintf(
@@ -484,24 +499,6 @@ func (h *Headscale) handleMachineRegistrationNew(
 			strings.TrimSuffix(h.cfg.ServerURL, "/"), MachinePublicKeyStripPrefix(machineKey))
 	}
 
-	if !registerRequest.Expiry.IsZero() {
-		log.Trace().
-			Caller().
-			Str("machine", machine.Name).
-			Time("expiry", registerRequest.Expiry).
-			Msg("Non-zero expiry time requested, adding to cache")
-		h.requestedExpiryCache.Set(
-			machineKey.String(),
-			registerRequest.Expiry,
-			requestedExpiryCacheExpiration,
-		)
-	}
-
-	machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
-
-	// save the NodeKey
-	h.db.Save(&machine)
-
 	respBody, err := encode(resp, &machineKey, h.privateKey)
 	if err != nil {
 		log.Error().
@@ -520,19 +517,21 @@ func (h *Headscale) handleAuthKey(
 	ctx *gin.Context,
 	machineKey key.MachinePublic,
 	registerRequest tailcfg.RegisterRequest,
-	machine Machine,
 ) {
+	machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
+
 	log.Debug().
 		Str("func", "handleAuthKey").
 		Str("machine", registerRequest.Hostinfo.Hostname).
 		Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
 	resp := tailcfg.RegisterResponse{}
+
 	pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey)
 	if err != nil {
 		log.Error().
 			Caller().
 			Str("func", "handleAuthKey").
-			Str("machine", machine.Name).
+			Str("machine", registerRequest.Hostinfo.Hostname).
 			Err(err).
 			Msg("Failed authentication via AuthKey")
 		resp.MachineAuthorized = false
@@ -541,76 +540,66 @@ func (h *Headscale) handleAuthKey(
 			log.Error().
 				Caller().
 				Str("func", "handleAuthKey").
-				Str("machine", machine.Name).
+				Str("machine", registerRequest.Hostinfo.Hostname).
 				Err(err).
 				Msg("Cannot encode message")
 			ctx.String(http.StatusInternalServerError, "")
-			machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
+			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
 				Inc()
 
 			return
 		}
+
 		ctx.Data(http.StatusUnauthorized, "application/json; charset=utf-8", respBody)
 		log.Error().
 			Caller().
 			Str("func", "handleAuthKey").
-			Str("machine", machine.Name).
+			Str("machine", registerRequest.Hostinfo.Hostname).
 			Msg("Failed authentication via AuthKey")
-		machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
+		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
 			Inc()
 
 		return
 	}
 
-	if machine.isRegistered() {
-		log.Trace().
-			Caller().
-			Str("machine", machine.Name).
-			Msg("machine already registered, reauthenticating")
+	log.Debug().
+		Str("func", "handleAuthKey").
+		Str("machine", registerRequest.Hostinfo.Hostname).
+		Msg("Authentication key was valid, proceeding to acquire IP addresses")
 
-		h.RefreshMachine(&machine, registerRequest.Expiry)
-	} else {
-		log.Debug().
-			Str("func", "handleAuthKey").
-			Str("machine", machine.Name).
-			Msg("Authentication key was valid, proceeding to acquire IP addresses")
+	nodeKey := NodePublicKeyStripPrefix(registerRequest.NodeKey)
+	now := time.Now().UTC()
 
-		h.ipAllocationMutex.Lock()
-
-		ips, err := h.getAvailableIPs()
-		if err != nil {
-			log.Error().
-				Caller().
-				Str("func", "handleAuthKey").
-				Str("machine", machine.Name).
-				Msg("Failed to find an available IP address")
-			machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
-				Inc()
-
-			return
-		}
-		log.Info().
-			Str("func", "handleAuthKey").
-			Str("machine", machine.Name).
-			Str("ips", strings.Join(ips.ToStringSlice(), ",")).
-			Msgf("Assigning %s to %s", strings.Join(ips.ToStringSlice(), ","), machine.Name)
-
-		machine.Expiry = &registerRequest.Expiry
-		machine.AuthKeyID = uint(pak.ID)
-		machine.IPAddresses = ips
-		machine.NamespaceID = pak.NamespaceID
-
-		machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
-		// we update it just in case
-		machine.Registered = true
-		machine.RegisterMethod = RegisterMethodAuthKey
-		h.db.Save(&machine)
-
-		h.ipAllocationMutex.Unlock()
+	machineToRegister := Machine{
+		Name:           registerRequest.Hostinfo.Hostname,
+		NamespaceID:    pak.Namespace.ID,
+		MachineKey:     machineKeyStr,
+		RegisterMethod: RegisterMethodAuthKey,
+		Expiry:         &registerRequest.Expiry,
+		NodeKey:        nodeKey,
+		LastSeen:       &now,
+		AuthKeyID:      uint(pak.ID),
 	}
 
-	pak.Used = true
-	h.db.Save(&pak)
+	machine, err := h.RegisterMachine(
+		machineToRegister,
+	)
+	if err != nil {
+		log.Error().
+			Caller().
+			Err(err).
+			Msg("could not register machine")
+		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
+			Inc()
+		ctx.String(
+			http.StatusInternalServerError,
+			"could not register machine",
+		)
+
+		return
+	}
+
+	h.UsePreAuthKey(pak)
 
 	resp.MachineAuthorized = true
 	resp.User = *pak.Namespace.toUser()
@@ -619,21 +608,21 @@ func (h *Headscale) handleAuthKey(
 		log.Error().
 			Caller().
 			Str("func", "handleAuthKey").
-			Str("machine", machine.Name).
+			Str("machine", registerRequest.Hostinfo.Hostname).
 			Err(err).
 			Msg("Cannot encode message")
-		machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
+		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
 			Inc()
 		ctx.String(http.StatusInternalServerError, "Extremely sad!")
 
 		return
 	}
-	machineRegistrations.WithLabelValues("new", "authkey", "success", machine.Namespace.Name).
+	machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name).
 		Inc()
 	ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
 	log.Info().
 		Str("func", "handleAuthKey").
-		Str("machine", machine.Name).
+		Str("machine", registerRequest.Hostinfo.Hostname).
 		Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
 		Msg("Successfully authenticated via AuthKey")
 }
diff --git a/app.go b/app.go
index 01fe730..763fdfe 100644
--- a/app.go
+++ b/app.go
@@ -55,8 +55,8 @@ const (
 	HTTPReadTimeout    = 30 * time.Second
 	privateKeyFileMode = 0o600
 
-	requestedExpiryCacheExpiration      = time.Minute * 5
-	requestedExpiryCacheCleanupInterval = time.Minute * 10
+	registerCacheExpiration = time.Minute * 15
+	registerCacheCleanup    = time.Minute * 20
 
 	errUnsupportedDatabase                 = Error("unsupported DB")
 	errUnsupportedLetsEncryptChallengeType = Error(
@@ -149,11 +149,10 @@ type Headscale struct {
 
 	lastStateChange sync.Map
 
-	oidcProvider   *oidc.Provider
-	oauth2Config   *oauth2.Config
-	oidcStateCache *cache.Cache
+	oidcProvider *oidc.Provider
+	oauth2Config *oauth2.Config
 
-	requestedExpiryCache *cache.Cache
+	registrationCache *cache.Cache
 
 	ipAllocationMutex sync.Mutex
 }
@@ -203,18 +202,18 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
 		return nil, errUnsupportedDatabase
 	}
 
-	requestedExpiryCache := cache.New(
-		requestedExpiryCacheExpiration,
-		requestedExpiryCacheCleanupInterval,
+	registrationCache := cache.New(
+		registerCacheExpiration,
+		registerCacheCleanup,
 	)
 
 	app := Headscale{
-		cfg:                  cfg,
-		dbType:               cfg.DBtype,
-		dbString:             dbString,
-		privateKey:           privKey,
-		aclRules:             tailcfg.FilterAllowAll, // default allowall
-		requestedExpiryCache: requestedExpiryCache,
+		cfg:               cfg,
+		dbType:            cfg.DBtype,
+		dbString:          dbString,
+		privateKey:        privKey,
+		aclRules:          tailcfg.FilterAllowAll, // default allowall
+		registrationCache: registrationCache,
 	}
 
 	err = app.initDB()
diff --git a/app_test.go b/app_test.go
index 53c703a..96036a1 100644
--- a/app_test.go
+++ b/app_test.go
@@ -5,7 +5,6 @@ import (
 	"os"
 	"testing"
 
-	"github.com/patrickmn/go-cache"
 	"gopkg.in/check.v1"
 	"inet.af/netaddr"
 )
@@ -50,10 +49,6 @@ func (s *Suite) ResetDB(c *check.C) {
 		cfg:      cfg,
 		dbType:   "sqlite3",
 		dbString: tmpDir + "/headscale_test.db",
-		requestedExpiryCache: cache.New(
-			requestedExpiryCacheExpiration,
-			requestedExpiryCacheCleanupInterval,
-		),
 	}
 	err = app.initDB()
 	if err != nil {
diff --git a/apple_mobileconfig.go b/apple_mobileconfig.go
index e5d9eed..69f61a6 100644
--- a/apple_mobileconfig.go
+++ b/apple_mobileconfig.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"html/template"
 	"net/http"
+	textTemplate "text/template"
 
 	"github.com/gin-gonic/gin"
 	"github.com/gofrs/uuid"
@@ -30,7 +31,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
 		<p><code>curl {{.Url}}/apple/ios</code></p>
 		-->
 		<p><code>curl {{.Url}}/apple/macos</code></p>
-		
+
 		<h2>Profiles</h2>
 
 		<!--
@@ -39,7 +40,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
 		    <a href="/apple/ios" download="headscale_ios.mobileconfig">iOS profile</a>
 		</p>
 		-->
-		
+
 		<h3>macOS</h3>
 		<p>Headscale can be set to the default server by installing a Headscale configuration profile:</p>
 		<p>
@@ -58,7 +59,7 @@ func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
 		<code>defaults write io.tailscale.ipn.macos ControlURL {{.URL}}</code>
 
 		<p>Restart Tailscale.app and log in.</p>
-	
+
 	</body>
 </html>`))
 
@@ -202,8 +203,8 @@ type AppleMobilePlatformConfig struct {
 	URL  string
 }
 
-var commonTemplate = template.Must(
-	template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
+var commonTemplate = textTemplate.Must(
+	textTemplate.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
   <dict>
@@ -229,7 +230,7 @@ var commonTemplate = template.Must(
 </plist>`),
 )
 
-var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
+var iosTemplate = textTemplate.Must(textTemplate.New("iosTemplate").Parse(`
     <dict>
         <key>PayloadType</key>
         <string>io.tailscale.ipn.ios</string>
diff --git a/cli_test.go b/cli_test.go
deleted file mode 100644
index 71f2bea..0000000
--- a/cli_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package headscale
-
-import (
-	"time"
-
-	"gopkg.in/check.v1"
-	"inet.af/netaddr"
-)
-
-func (s *Suite) TestRegisterMachine(c *check.C) {
-	namespace, err := app.CreateNamespace("test")
-	c.Assert(err, check.IsNil)
-
-	now := time.Now().UTC()
-
-	machine := Machine{
-		ID:          0,
-		MachineKey:  "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
-		NodeKey:     "bar",
-		DiscoKey:    "faa",
-		Name:        "testmachine",
-		NamespaceID: namespace.ID,
-		IPAddresses: []netaddr.IP{netaddr.MustParseIP("10.0.0.1")},
-		Expiry:      &now,
-	}
-	err = app.db.Save(&machine).Error
-	c.Assert(err, check.IsNil)
-
-	_, err = app.GetMachine(namespace.Name, machine.Name)
-	c.Assert(err, check.IsNil)
-
-	machineAfterRegistering, err := app.RegisterMachine(
-		machine.MachineKey,
-		namespace.Name,
-	)
-	c.Assert(err, check.IsNil)
-	c.Assert(machineAfterRegistering.Registered, check.Equals, true)
-
-	_, err = machineAfterRegistering.GetHostInfo()
-	c.Assert(err, check.IsNil)
-}
diff --git a/cmd/headscale/cli/api_key.go b/cmd/headscale/cli/api_key.go
index 975149f..06099aa 100644
--- a/cmd/headscale/cli/api_key.go
+++ b/cmd/headscale/cli/api_key.go
@@ -36,13 +36,15 @@ func init() {
 }
 
 var apiKeysCmd = &cobra.Command{
-	Use:   "apikeys",
-	Short: "Handle the Api keys in Headscale",
+	Use:     "apikeys",
+	Short:   "Handle the Api keys in Headscale",
+	Aliases: []string{"apikey", "api"},
 }
 
 var listAPIKeys = &cobra.Command{
-	Use:   "list",
-	Short: "List the Api keys for headscale",
+	Use:     "list",
+	Short:   "List the Api keys for headscale",
+	Aliases: []string{"ls", "show"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
@@ -107,6 +109,7 @@ var createAPIKeyCmd = &cobra.Command{
 Creates a new Api key, the Api key is only visible on creation
 and cannot be retrieved again.
 If you loose a key, create a new one and revoke (expire) the old one.`,
+	Aliases: []string{"c", "new"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
@@ -144,7 +147,7 @@ If you loose a key, create a new one and revoke (expire) the old one.`,
 var expireAPIKeyCmd = &cobra.Command{
 	Use:     "expire",
 	Short:   "Expire an ApiKey",
-	Aliases: []string{"revoke"},
+	Aliases: []string{"revoke", "exp", "e"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
diff --git a/cmd/headscale/cli/generate.go b/cmd/headscale/cli/generate.go
index 2484414..3590641 100644
--- a/cmd/headscale/cli/generate.go
+++ b/cmd/headscale/cli/generate.go
@@ -13,8 +13,9 @@ func init() {
 }
 
 var generateCmd = &cobra.Command{
-	Use:   "generate",
-	Short: "Generate commands",
+	Use:     "generate",
+	Short:   "Generate commands",
+	Aliases: []string{"gen"},
 }
 
 var generatePrivateKeyCmd = &cobra.Command{
diff --git a/cmd/headscale/cli/namespaces.go b/cmd/headscale/cli/namespaces.go
index 361e9be..729e213 100644
--- a/cmd/headscale/cli/namespaces.go
+++ b/cmd/headscale/cli/namespaces.go
@@ -25,13 +25,15 @@ const (
 )
 
 var namespaceCmd = &cobra.Command{
-	Use:   "namespaces",
-	Short: "Manage the namespaces of Headscale",
+	Use:     "namespaces",
+	Short:   "Manage the namespaces of Headscale",
+	Aliases: []string{"namespace", "ns", "user", "users"},
 }
 
 var createNamespaceCmd = &cobra.Command{
-	Use:   "create NAME",
-	Short: "Creates a new namespace",
+	Use:     "create NAME",
+	Short:   "Creates a new namespace",
+	Aliases: []string{"c", "new"},
 	Args: func(cmd *cobra.Command, args []string) error {
 		if len(args) < 1 {
 			return errMissingParameter
@@ -72,8 +74,9 @@ var createNamespaceCmd = &cobra.Command{
 }
 
 var destroyNamespaceCmd = &cobra.Command{
-	Use:   "destroy NAME",
-	Short: "Destroys a namespace",
+	Use:     "destroy NAME",
+	Short:   "Destroys a namespace",
+	Aliases: []string{"delete"},
 	Args: func(cmd *cobra.Command, args []string) error {
 		if len(args) < 1 {
 			return errMissingParameter
@@ -144,8 +147,9 @@ var destroyNamespaceCmd = &cobra.Command{
 }
 
 var listNamespacesCmd = &cobra.Command{
-	Use:   "list",
-	Short: "List all the namespaces",
+	Use:     "list",
+	Short:   "List all the namespaces",
+	Aliases: []string{"ls", "show"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
@@ -197,8 +201,9 @@ var listNamespacesCmd = &cobra.Command{
 }
 
 var renameNamespaceCmd = &cobra.Command{
-	Use:   "rename OLD_NAME NEW_NAME",
-	Short: "Renames a namespace",
+	Use:     "rename OLD_NAME NEW_NAME",
+	Short:   "Renames a namespace",
+	Aliases: []string{"mv"},
 	Args: func(cmd *cobra.Command, args []string) error {
 		expectedArguments := 2
 		if len(args) < expectedArguments {
diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go
index a05339c..0abe87b 100644
--- a/cmd/headscale/cli/nodes.go
+++ b/cmd/headscale/cli/nodes.go
@@ -49,8 +49,9 @@ func init() {
 }
 
 var nodeCmd = &cobra.Command{
-	Use:   "nodes",
-	Short: "Manage the nodes of Headscale",
+	Use:     "nodes",
+	Short:   "Manage the nodes of Headscale",
+	Aliases: []string{"node", "machine", "machines"},
 }
 
 var registerNodeCmd = &cobra.Command{
@@ -104,8 +105,9 @@ var registerNodeCmd = &cobra.Command{
 }
 
 var listNodesCmd = &cobra.Command{
-	Use:   "list",
-	Short: "List nodes",
+	Use:     "list",
+	Short:   "List nodes",
+	Aliases: []string{"ls", "show"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 		namespace, err := cmd.Flags().GetString("namespace")
@@ -164,7 +166,7 @@ var expireNodeCmd = &cobra.Command{
 	Use:     "expire",
 	Short:   "Expire (log out) a machine in your network",
 	Long:    "Expiring a node will keep the node in the database and force it to reauthenticate.",
-	Aliases: []string{"logout"},
+	Aliases: []string{"logout", "exp", "e"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
@@ -206,8 +208,9 @@ var expireNodeCmd = &cobra.Command{
 }
 
 var deleteNodeCmd = &cobra.Command{
-	Use:   "delete",
-	Short: "Delete a node",
+	Use:     "delete",
+	Short:   "Delete a node",
+	Aliases: []string{"del"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
diff --git a/cmd/headscale/cli/preauthkeys.go b/cmd/headscale/cli/preauthkeys.go
index 580184f..950cbcc 100644
--- a/cmd/headscale/cli/preauthkeys.go
+++ b/cmd/headscale/cli/preauthkeys.go
@@ -35,13 +35,15 @@ func init() {
 }
 
 var preauthkeysCmd = &cobra.Command{
-	Use:   "preauthkeys",
-	Short: "Handle the preauthkeys in Headscale",
+	Use:     "preauthkeys",
+	Short:   "Handle the preauthkeys in Headscale",
+	Aliases: []string{"preauthkey", "authkey", "pre"},
 }
 
 var listPreAuthKeys = &cobra.Command{
-	Use:   "list",
-	Short: "List the preauthkeys for this namespace",
+	Use:     "list",
+	Short:   "List the preauthkeys for this namespace",
+	Aliases: []string{"ls", "show"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
@@ -118,8 +120,9 @@ var listPreAuthKeys = &cobra.Command{
 }
 
 var createPreAuthKeyCmd = &cobra.Command{
-	Use:   "create",
-	Short: "Creates a new preauthkey in the specified namespace",
+	Use:     "create",
+	Short:   "Creates a new preauthkey in the specified namespace",
+	Aliases: []string{"c", "new"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
@@ -172,8 +175,9 @@ var createPreAuthKeyCmd = &cobra.Command{
 }
 
 var expirePreAuthKeyCmd = &cobra.Command{
-	Use:   "expire KEY",
-	Short: "Expire a preauthkey",
+	Use:     "expire KEY",
+	Short:   "Expire a preauthkey",
+	Aliases: []string{"revoke", "exp", "e"},
 	Args: func(cmd *cobra.Command, args []string) error {
 		if len(args) < 1 {
 			return errMissingParameter
diff --git a/cmd/headscale/cli/routes.go b/cmd/headscale/cli/routes.go
index ced1a0b..dc060fb 100644
--- a/cmd/headscale/cli/routes.go
+++ b/cmd/headscale/cli/routes.go
@@ -35,13 +35,15 @@ func init() {
 }
 
 var routesCmd = &cobra.Command{
-	Use:   "routes",
-	Short: "Manage the routes of Headscale",
+	Use:     "routes",
+	Short:   "Manage the routes of Headscale",
+	Aliases: []string{"r", "route"},
 }
 
 var listRoutesCmd = &cobra.Command{
-	Use:   "list",
-	Short: "List routes advertised and enabled by a given node",
+	Use:     "list",
+	Short:   "List routes advertised and enabled by a given node",
+	Aliases: []string{"ls", "show"},
 	Run: func(cmd *cobra.Command, args []string) {
 		output, _ := cmd.Flags().GetString("output")
 
diff --git a/config-example.yaml b/config-example.yaml
index 0939d6c..c28b608 100644
--- a/config-example.yaml
+++ b/config-example.yaml
@@ -138,7 +138,8 @@ tls_key_path: ""
 log_level: info
 
 # Path to a file containg ACL policies.
-# Recommended path: /etc/headscale/acl.hujson
+# ACLs can be defined as YAML or HUJSON.
+# https://tailscale.com/kb/1018/acls/
 acl_policy_path: ""
 
 ## DNS
diff --git a/db.go b/db.go
index 8db7bc6..9130d90 100644
--- a/db.go
+++ b/db.go
@@ -1,13 +1,19 @@
 package headscale
 
 import (
+	"database/sql/driver"
+	"encoding/json"
 	"errors"
+	"fmt"
 	"time"
 
 	"github.com/glebarez/sqlite"
+	"github.com/rs/zerolog/log"
 	"gorm.io/driver/postgres"
 	"gorm.io/gorm"
 	"gorm.io/gorm/logger"
+	"inet.af/netaddr"
+	"tailscale.com/tailcfg"
 )
 
 const (
@@ -34,6 +40,38 @@ func (h *Headscale) initDB() error {
 
 	_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses")
 
+	// If the Machine table has a column for registered,
+	// find all occourences of "false" and drop them. Then
+	// remove the column.
+	if db.Migrator().HasColumn(&Machine{}, "registered") {
+		log.Info().
+			Msg(`Database has legacy "registered" column in machine, removing...`)
+
+		machines := Machines{}
+		if err := h.db.Not("registered").Find(&machines).Error; err != nil {
+			log.Error().Err(err).Msg("Error accessing db")
+		}
+
+		for _, machine := range machines {
+			log.Info().
+				Str("machine", machine.Name).
+				Str("machine_key", machine.MachineKey).
+				Msg("Deleting unregistered machine")
+			if err := h.db.Delete(&Machine{}, machine.ID).Error; err != nil {
+				log.Error().
+					Err(err).
+					Str("machine", machine.Name).
+					Str("machine_key", machine.MachineKey).
+					Msg("Error deleting unregistered machine")
+			}
+		}
+
+		err := db.Migrator().DropColumn(&Machine{}, "registered")
+		if err != nil {
+			log.Error().Err(err).Msg("Error dropping registered column")
+		}
+	}
+
 	err = db.AutoMigrate(&Machine{})
 	if err != nil {
 		return err
@@ -141,3 +179,74 @@ func (h *Headscale) setValue(key string, value string) error {
 
 	return nil
 }
+
+// This is a "wrapper" type around tailscales
+// Hostinfo to allow us to add database "serialization"
+// methods. This allows us to use a typed values throughout
+// the code and not have to marshal/unmarshal and error
+// check all over the code.
+type HostInfo tailcfg.Hostinfo
+
+func (hi *HostInfo) Scan(destination interface{}) error {
+	switch value := destination.(type) {
+	case []byte:
+		return json.Unmarshal(value, hi)
+
+	case string:
+		return json.Unmarshal([]byte(value), hi)
+
+	default:
+		return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
+	}
+}
+
+// Value return json value, implement driver.Valuer interface.
+func (hi HostInfo) Value() (driver.Value, error) {
+	bytes, err := json.Marshal(hi)
+
+	return string(bytes), err
+}
+
+type IPPrefixes []netaddr.IPPrefix
+
+func (i *IPPrefixes) Scan(destination interface{}) error {
+	switch value := destination.(type) {
+	case []byte:
+		return json.Unmarshal(value, i)
+
+	case string:
+		return json.Unmarshal([]byte(value), i)
+
+	default:
+		return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
+	}
+}
+
+// Value return json value, implement driver.Valuer interface.
+func (i IPPrefixes) Value() (driver.Value, error) {
+	bytes, err := json.Marshal(i)
+
+	return string(bytes), err
+}
+
+type StringList []string
+
+func (i *StringList) Scan(destination interface{}) error {
+	switch value := destination.(type) {
+	case []byte:
+		return json.Unmarshal(value, i)
+
+	case string:
+		return json.Unmarshal([]byte(value), i)
+
+	default:
+		return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
+	}
+}
+
+// Value return json value, implement driver.Valuer interface.
+func (i StringList) Value() (driver.Value, error) {
+	bytes, err := json.Marshal(i)
+
+	return string(bytes), err
+}
diff --git a/dns_test.go b/dns_test.go
index 23a34ca..5fd30d3 100644
--- a/dns_test.go
+++ b/dns_test.go
@@ -164,7 +164,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_1",
 		NamespaceID:    namespaceShared1.ID,
 		Namespace:      *namespaceShared1,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
 		AuthKeyID:      uint(preAuthKeyInShared1.ID),
@@ -182,7 +181,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_2",
 		NamespaceID:    namespaceShared2.ID,
 		Namespace:      *namespaceShared2,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
 		AuthKeyID:      uint(preAuthKeyInShared2.ID),
@@ -200,7 +198,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_3",
 		NamespaceID:    namespaceShared3.ID,
 		Namespace:      *namespaceShared3,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
 		AuthKeyID:      uint(preAuthKeyInShared3.ID),
@@ -218,7 +215,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_4",
 		NamespaceID:    namespaceShared1.ID,
 		Namespace:      *namespaceShared1,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 		AuthKeyID:      uint(PreAuthKey2InShared1.ID),
@@ -311,7 +307,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_1",
 		NamespaceID:    namespaceShared1.ID,
 		Namespace:      *namespaceShared1,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
 		AuthKeyID:      uint(preAuthKeyInShared1.ID),
@@ -329,7 +324,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_2",
 		NamespaceID:    namespaceShared2.ID,
 		Namespace:      *namespaceShared2,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
 		AuthKeyID:      uint(preAuthKeyInShared2.ID),
@@ -347,7 +341,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_3",
 		NamespaceID:    namespaceShared3.ID,
 		Namespace:      *namespaceShared3,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
 		AuthKeyID:      uint(preAuthKeyInShared3.ID),
@@ -365,7 +358,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
 		Name:           "test_get_shared_nodes_4",
 		NamespaceID:    namespaceShared1.ID,
 		Namespace:      *namespaceShared1,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 		AuthKeyID:      uint(preAuthKey2InShared1.ID),
diff --git a/gen/go/headscale/v1/machine.pb.go b/gen/go/headscale/v1/machine.pb.go
index 9a5064f..b25db29 100644
--- a/gen/go/headscale/v1/machine.pb.go
+++ b/gen/go/headscale/v1/machine.pb.go
@@ -85,13 +85,12 @@ type Machine struct {
 	IpAddresses          []string               `protobuf:"bytes,5,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"`
 	Name                 string                 `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
 	Namespace            *Namespace             `protobuf:"bytes,7,opt,name=namespace,proto3" json:"namespace,omitempty"`
-	Registered           bool                   `protobuf:"varint,8,opt,name=registered,proto3" json:"registered,omitempty"`
-	RegisterMethod       RegisterMethod         `protobuf:"varint,9,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"`
-	LastSeen             *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"`
-	LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=last_successful_update,json=lastSuccessfulUpdate,proto3" json:"last_successful_update,omitempty"`
-	Expiry               *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=expiry,proto3" json:"expiry,omitempty"`
-	PreAuthKey           *PreAuthKey            `protobuf:"bytes,13,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"`
-	CreatedAt            *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+	LastSeen             *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"`
+	LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=last_successful_update,json=lastSuccessfulUpdate,proto3" json:"last_successful_update,omitempty"`
+	Expiry               *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expiry,proto3" json:"expiry,omitempty"`
+	PreAuthKey           *PreAuthKey            `protobuf:"bytes,11,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"`
+	CreatedAt            *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+	RegisterMethod       RegisterMethod         `protobuf:"varint,13,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"`
 }
 
 func (x *Machine) Reset() {
@@ -175,20 +174,6 @@ func (x *Machine) GetNamespace() *Namespace {
 	return nil
 }
 
-func (x *Machine) GetRegistered() bool {
-	if x != nil {
-		return x.Registered
-	}
-	return false
-}
-
-func (x *Machine) GetRegisterMethod() RegisterMethod {
-	if x != nil {
-		return x.RegisterMethod
-	}
-	return RegisterMethod_REGISTER_METHOD_UNSPECIFIED
-}
-
 func (x *Machine) GetLastSeen() *timestamppb.Timestamp {
 	if x != nil {
 		return x.LastSeen
@@ -224,6 +209,13 @@ func (x *Machine) GetCreatedAt() *timestamppb.Timestamp {
 	return nil
 }
 
+func (x *Machine) GetRegisterMethod() RegisterMethod {
+	if x != nil {
+		return x.RegisterMethod
+	}
+	return RegisterMethod_REGISTER_METHOD_UNSPECIFIED
+}
+
 type RegisterMachineRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -822,7 +814,7 @@ var file_headscale_v1_machine_proto_rawDesc = []byte{
 	0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
 	0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x68, 0x65, 0x61, 0x64, 0x73,
 	0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b,
-	0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63,
+	0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdd, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63,
 	0x68, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
 	0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f,
 	0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69,
@@ -836,33 +828,31 @@ var file_headscale_v1_machine_proto_rawDesc = []byte{
 	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
 	0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
 	0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
-	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72,
-	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72,
-	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09,
-	0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
-	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
-	0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
-	0x6f, 0x64, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18,
-	0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-	0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c,
-	0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75,
-	0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
-	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63,
-	0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a,
-	0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
-	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
-	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72,
-	0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65,
-	0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
-	0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
-	0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a,
-	0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63,
-	0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69,
+	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x6c,
+	0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74,
+	0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63,
+	0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x09,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
+	0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c,
+	0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79,
+	0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+	0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72,
+	0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
+	0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41,
+	0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
+	0x64, 0x5f, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
+	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
+	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
+	0x74, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65,
+	0x74, 0x68, 0x6f, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61,
+	0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
+	0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
+	0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69,
 	0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
 	0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,
 	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
@@ -962,12 +952,12 @@ var file_headscale_v1_machine_proto_goTypes = []interface{}{
 }
 var file_headscale_v1_machine_proto_depIdxs = []int32{
 	14, // 0: headscale.v1.Machine.namespace:type_name -> headscale.v1.Namespace
-	0,  // 1: headscale.v1.Machine.register_method:type_name -> headscale.v1.RegisterMethod
-	15, // 2: headscale.v1.Machine.last_seen:type_name -> google.protobuf.Timestamp
-	15, // 3: headscale.v1.Machine.last_successful_update:type_name -> google.protobuf.Timestamp
-	15, // 4: headscale.v1.Machine.expiry:type_name -> google.protobuf.Timestamp
-	16, // 5: headscale.v1.Machine.pre_auth_key:type_name -> headscale.v1.PreAuthKey
-	15, // 6: headscale.v1.Machine.created_at:type_name -> google.protobuf.Timestamp
+	15, // 1: headscale.v1.Machine.last_seen:type_name -> google.protobuf.Timestamp
+	15, // 2: headscale.v1.Machine.last_successful_update:type_name -> google.protobuf.Timestamp
+	15, // 3: headscale.v1.Machine.expiry:type_name -> google.protobuf.Timestamp
+	16, // 4: headscale.v1.Machine.pre_auth_key:type_name -> headscale.v1.PreAuthKey
+	15, // 5: headscale.v1.Machine.created_at:type_name -> google.protobuf.Timestamp
+	0,  // 6: headscale.v1.Machine.register_method:type_name -> headscale.v1.RegisterMethod
 	1,  // 7: headscale.v1.RegisterMachineResponse.machine:type_name -> headscale.v1.Machine
 	1,  // 8: headscale.v1.GetMachineResponse.machine:type_name -> headscale.v1.Machine
 	1,  // 9: headscale.v1.ExpireMachineResponse.machine:type_name -> headscale.v1.Machine
diff --git a/gen/openapiv2/headscale/v1/headscale.swagger.json b/gen/openapiv2/headscale/v1/headscale.swagger.json
index db348f4..8d808f9 100644
--- a/gen/openapiv2/headscale/v1/headscale.swagger.json
+++ b/gen/openapiv2/headscale/v1/headscale.swagger.json
@@ -885,12 +885,6 @@
         "namespace": {
           "$ref": "#/definitions/v1Namespace"
         },
-        "registered": {
-          "type": "boolean"
-        },
-        "registerMethod": {
-          "$ref": "#/definitions/v1RegisterMethod"
-        },
         "lastSeen": {
           "type": "string",
           "format": "date-time"
@@ -909,6 +903,9 @@
         "createdAt": {
           "type": "string",
           "format": "date-time"
+        },
+        "registerMethod": {
+          "$ref": "#/definitions/v1RegisterMethod"
         }
       }
     },
diff --git a/go.mod b/go.mod
index 8b721fa..a121826 100644
--- a/go.mod
+++ b/go.mod
@@ -13,12 +13,12 @@ require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3
 	github.com/infobloxopen/protoc-gen-gorm v1.1.0
-	github.com/klauspost/compress v1.14.2
+	github.com/klauspost/compress v1.14.4
 	github.com/ory/dockertest/v3 v3.8.1
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/philip-bui/grpc-zerolog v1.0.1
 	github.com/prometheus/client_golang v1.12.1
-	github.com/pterm/pterm v0.12.36
+	github.com/pterm/pterm v0.12.37
 	github.com/rs/zerolog v1.26.1
 	github.com/spf13/cobra v1.3.0
 	github.com/spf13/viper v1.10.1
@@ -27,24 +27,24 @@ require (
 	github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
 	github.com/zsais/go-gin-prometheus v0.1.0
 	golang.org/x/crypto v0.0.0-20220214200702-86341886e292
-	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
+	golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
-	google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336
+	google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7
 	google.golang.org/grpc v1.44.0
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
 	google.golang.org/protobuf v1.27.1
 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
 	gopkg.in/yaml.v2 v2.4.0
-	gorm.io/datatypes v1.0.5
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 	gorm.io/driver/postgres v1.3.1
 	gorm.io/gorm v1.23.1
 	inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
-	tailscale.com v1.20.4
+	tailscale.com v1.22.0
 )
 
 require (
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
-	github.com/Microsoft/go-winio v0.5.1 // indirect
+	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
 	github.com/atomicgo/cursor v0.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
@@ -52,13 +52,14 @@ require (
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/containerd/continuity v0.2.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/denisenkom/go-mssqldb v0.12.0 // indirect
 	github.com/docker/cli v20.10.12+incompatible // indirect
 	github.com/docker/docker v20.10.12+incompatible // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
-	github.com/glebarez/go-sqlite v1.14.7 // indirect
+	github.com/glebarez/go-sqlite v1.14.8 // indirect
 	github.com/go-playground/locales v0.14.0 // indirect
 	github.com/go-playground/universal-translator v0.18.0 // indirect
 	github.com/go-playground/validator/v10 v10.10.0 // indirect
@@ -92,7 +93,7 @@ require (
 	github.com/kr/text v0.2.0 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/lib/pq v1.10.3 // indirect
-	github.com/magiconair/properties v1.8.5 // indirect
+	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
@@ -121,7 +122,7 @@ require (
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.2.0 // indirect
-	github.com/ugorji/go/codec v1.2.6 // indirect
+	github.com/ugorji/go/codec v1.2.7 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
@@ -129,20 +130,16 @@ require (
 	go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
 	go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
 	go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
-	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
-	golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
+	golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
+	golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
 	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
-	gorm.io/driver/mysql v1.3.2 // indirect
-	gorm.io/driver/sqlite v1.3.1 // indirect
-	gorm.io/driver/sqlserver v1.3.1 // indirect
-	modernc.org/libc v1.14.3 // indirect
+	modernc.org/libc v1.14.5 // indirect
 	modernc.org/mathutil v1.4.1 // indirect
 	modernc.org/memory v1.0.5 // indirect
-	modernc.org/sqlite v1.14.5 // indirect
+	modernc.org/sqlite v1.14.7 // indirect
 	sigs.k8s.io/yaml v1.3.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 20eec3f..558d3d7 100644
--- a/go.sum
+++ b/go.sum
@@ -73,8 +73,9 @@ github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzX
 github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
 github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
-github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
 github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
 github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
@@ -212,8 +213,9 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
 github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/glebarez/go-sqlite v1.14.7 h1:eXrKp59O5eWBfxv2Xfq5d7uex4+clKrOtWfMzzGSkoM=
 github.com/glebarez/go-sqlite v1.14.7/go.mod h1:TKAw5tjyB/ocvVht7Xv4772qRAun5CG/xLCEbkDwNUc=
+github.com/glebarez/go-sqlite v1.14.8 h1:30RsIS/olgfOMr7SxiCaYhpq50BTteA/CUKaWVOOHYg=
+github.com/glebarez/go-sqlite v1.14.8/go.mod h1:gf9QVsKCYMcu+7nd+ZbDqvXnEXEb22qLcqRUQ9XEI34=
 github.com/glebarez/sqlite v1.3.5 h1:R9op5nxb9Z10t4VXQSdAVyqRalLhWdLrlaT/iuvOGHI=
 github.com/glebarez/sqlite v1.3.5/go.mod h1:ZffEtp/afVhV+jvIzQi8wlYEIkuGAYshr9OPKM/NmQc=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -412,7 +414,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
 github.com/infobloxopen/atlas-app-toolkit v0.24.1-0.20210416193901-4c7518b07e08/go.mod h1:9BTHnpff654rY1J8KxSUOLJ+ZUDn2Vi3mmk26gQDo1M=
 github.com/infobloxopen/protoc-gen-gorm v1.1.0 h1:l6JKEkqMTFbtoGIfQmh/aOy7KfljgX4ql772LtG4kas=
 github.com/infobloxopen/protoc-gen-gorm v1.1.0/go.mod h1:ohzLmmFMWQztw2RBHunfjKSCjTPUW4JvbgU1Mdazwxg=
-github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
 github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
 github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
 github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
@@ -434,7 +435,6 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W
 github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
 github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
 github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
 github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
@@ -450,7 +450,6 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
 github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
 github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
@@ -458,7 +457,6 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
 github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
 github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
 github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
@@ -474,8 +472,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
-github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
-github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
 github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -497,8 +493,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw=
-github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4=
+github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@@ -535,8 +531,9 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8
 github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -558,7 +555,6 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ=
 github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -671,8 +667,8 @@ github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY
 github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
 github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
 github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
-github.com/pterm/pterm v0.12.36 h1:Ui5zZj7xA8lXR0CxWXlKGCQMW1cZVUMOS8jEXs6ur/g=
-github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
+github.com/pterm/pterm v0.12.37 h1:QGOyuaDUmY3yTbP0k6i0uPNqNHA9YofEBQDy0tIyKTA=
+github.com/pterm/pterm v0.12.37/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -762,10 +758,10 @@ github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9
 github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
-github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
+github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
-github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
+github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
@@ -845,8 +841,6 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 h1:XdAboW3BNMv9ocSCOk/u1MFioZGzCNkiJZ19v9Oe3Ig=
-golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -940,8 +934,9 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -959,8 +954,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1063,9 +1059,10 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1274,8 +1271,8 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6
 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336 h1:RK2ysGpQApbI6U7xn+ROT2rrm08lE/t8AcGqG8XI1CY=
-google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1359,23 +1356,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/datatypes v1.0.5 h1:3vHCfg4Bz8SDx83zE+ASskF+g/j0kWrcKrY9jFUyAl0=
-gorm.io/datatypes v1.0.5/go.mod h1:acG/OHGwod+1KrbwPL1t+aavb7jOBOETeyl5M8K5VQs=
-gorm.io/driver/mysql v1.2.2/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
-gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y=
-gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
-gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
-gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
-gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
-gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
 gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
 gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
-gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
-gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
-gorm.io/driver/sqlserver v1.3.1 h1:F5t6ScMzOgy1zukRTIZgLZwKahgt3q1woAILVolKpOI=
-gorm.io/driver/sqlserver v1.3.1/go.mod h1:w25Vrx2BG+CJNUu/xKbFhaKlGxT/nzRkhWCCoptX8tQ=
-gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
-gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
 gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
 gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
@@ -1450,7 +1432,10 @@ modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6
 modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
 modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
 modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
+modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
+modernc.org/ccgo/v3 v3.15.13/go.mod h1:QHtvdpeODlXjdK3tsbpyK+7U9JV4PQsrPGIbtmc0KfY=
 modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/ccorpus v1.11.4/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
 modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
 modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
 modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
@@ -1494,8 +1479,9 @@ modernc.org/libc v1.13.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
 modernc.org/libc v1.13.2/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
 modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
 modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
-modernc.org/libc v1.14.3 h1:ruQJ8VDhnWkUR/otUG/Ksw+sWHUw9cPAq6mjDaY/Y7c=
 modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
+modernc.org/libc v1.14.5 h1:DAHvwGoVRDZs5iJXnX9RJrgXSsorupCWmJ2ac964Owk=
+modernc.org/libc v1.14.5/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
 modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
@@ -1505,10 +1491,12 @@ modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
 modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
 modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
 modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/sqlite v1.14.5 h1:bYrrjwH9Y7QUGk1MbchZDhRfmpGuEAs/D45sVjNbfvs=
 modernc.org/sqlite v1.14.5/go.mod h1:YyX5Rx0WbXokitdWl2GJIDy4BrPxBP0PwwhpXOHCDLE=
+modernc.org/sqlite v1.14.7 h1:A+6rGjtRQbt9SORXfV+hUyXOP3mDf7J5uz+EES/CNPE=
+modernc.org/sqlite v1.14.7/go.mod h1:yiCvMv3HblGmzENNIaNtFhfaNIwcla4u2JQEwJPzfEc=
 modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
 modernc.org/tcl v1.10.0/go.mod h1:WzWapmP/7dHVhFoyPpEaNSVTL8xtewhouN/cqSJ5A2s=
+modernc.org/tcl v1.11.0/go.mod h1:zsTUpbQ+NxQEjOjCUlImDLPv1sG8Ww0qp66ZvyOxCgw=
 modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
 modernc.org/z v1.2.21/go.mod h1:uXrObx4pGqXWIMliC5MiKuwAyMrltzwpteOFUP1PWCc=
 modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g=
@@ -1517,5 +1505,5 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
 sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
-tailscale.com v1.20.4 h1:7cl/Q2Sbo2Jb2dX7zA+Exbbl7DT5UGZ4iGhQ2xj23X0=
-tailscale.com v1.20.4/go.mod h1:kjVy3ji2OH5lZhPLIIRacoY3CN4Bo3Yyb2mtoM8nfJ4=
+tailscale.com v1.22.0 h1:/a1f6eKEl9vL/wGFP8mkhe7O1zDRGtWa9Ft2rGp5N80=
+tailscale.com v1.22.0/go.mod h1:D2zuDnjHT7v4aCt71c4+ytQUUAGpnypW+DoubYLaHjg=
diff --git a/grpcv1.go b/grpcv1.go
index 60e181d..647e599 100644
--- a/grpcv1.go
+++ b/grpcv1.go
@@ -3,12 +3,10 @@ package headscale
 
 import (
 	"context"
-	"encoding/json"
 	"time"
 
 	"github.com/juanfont/headscale/gen/go/headscale/v1"
 	"github.com/rs/zerolog/log"
-	"gorm.io/datatypes"
 	"tailscale.com/tailcfg"
 )
 
@@ -159,9 +157,11 @@ func (api headscaleV1APIServer) RegisterMachine(
 		Str("namespace", request.GetNamespace()).
 		Str("machine_key", request.GetKey()).
 		Msg("Registering machine")
-	machine, err := api.h.RegisterMachine(
+
+	machine, err := api.h.RegisterMachineFromAuthCallback(
 		request.GetKey(),
 		request.GetNamespace(),
+		RegisterMethodCLI,
 	)
 	if err != nil {
 		return nil, err
@@ -262,13 +262,8 @@ func (api headscaleV1APIServer) GetMachineRoute(
 		return nil, err
 	}
 
-	routes, err := machine.RoutesToProto()
-	if err != nil {
-		return nil, err
-	}
-
 	return &v1.GetMachineRouteResponse{
-		Routes: routes,
+		Routes: machine.RoutesToProto(),
 	}, nil
 }
 
@@ -286,13 +281,8 @@ func (api headscaleV1APIServer) EnableMachineRoutes(
 		return nil, err
 	}
 
-	routes, err := machine.RoutesToProto()
-	if err != nil {
-		return nil, err
-	}
-
 	return &v1.EnableMachineRoutesResponse{
-		Routes: routes,
+		Routes: machine.RoutesToProto(),
 	}, nil
 }
 
@@ -379,13 +369,6 @@ func (api headscaleV1APIServer) DebugCreateMachine(
 		Hostname:    "DebugTestMachine",
 	}
 
-	log.Trace().Caller().Interface("hostinfo", hostinfo).Msg("")
-
-	hostinfoJson, err := json.Marshal(hostinfo)
-	if err != nil {
-		return nil, err
-	}
-
 	newMachine := Machine{
 		MachineKey: request.GetKey(),
 		Name:       request.GetName(),
@@ -395,14 +378,14 @@ func (api headscaleV1APIServer) DebugCreateMachine(
 		LastSeen:             &time.Time{},
 		LastSuccessfulUpdate: &time.Time{},
 
-		HostInfo: datatypes.JSON(hostinfoJson),
+		HostInfo: HostInfo(hostinfo),
 	}
 
-	// log.Trace().Caller().Interface("machine", newMachine).Msg("")
-
-	if err := api.h.db.Create(&newMachine).Error; err != nil {
-		return nil, err
-	}
+	api.h.registrationCache.Set(
+		request.GetKey(),
+		newMachine,
+		registerCacheExpiration,
+	)
 
 	return &v1.DebugCreateMachineResponse{Machine: newMachine.toProto()}, nil
 }
diff --git a/integration_cli_test.go b/integration_cli_test.go
index aae80cb..7ce0758 100644
--- a/integration_cli_test.go
+++ b/integration_cli_test.go
@@ -621,12 +621,6 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 	assert.Equal(s.T(), "machine-4", listAll[3].Name)
 	assert.Equal(s.T(), "machine-5", listAll[4].Name)
 
-	assert.True(s.T(), listAll[0].Registered)
-	assert.True(s.T(), listAll[1].Registered)
-	assert.True(s.T(), listAll[2].Registered)
-	assert.True(s.T(), listAll[3].Registered)
-	assert.True(s.T(), listAll[4].Registered)
-
 	otherNamespaceMachineKeys := []string{
 		"b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e",
 		"dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584",
@@ -710,9 +704,6 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 	assert.Equal(s.T(), "otherNamespace-machine-1", listAllWithotherNamespace[5].Name)
 	assert.Equal(s.T(), "otherNamespace-machine-2", listAllWithotherNamespace[6].Name)
 
-	assert.True(s.T(), listAllWithotherNamespace[5].Registered)
-	assert.True(s.T(), listAllWithotherNamespace[6].Registered)
-
 	// Test list all nodes after added otherNamespace
 	listOnlyotherNamespaceMachineNamespaceResult, err := ExecuteCommand(
 		&s.headscale,
@@ -752,9 +743,6 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
 		listOnlyotherNamespaceMachineNamespace[1].Name,
 	)
 
-	assert.True(s.T(), listOnlyotherNamespaceMachineNamespace[0].Registered)
-	assert.True(s.T(), listOnlyotherNamespaceMachineNamespace[1].Registered)
-
 	// Delete a machines
 	_, err = ExecuteCommand(
 		&s.headscale,
@@ -979,7 +967,6 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
 
 	assert.Equal(s.T(), uint64(1), machine.Id)
 	assert.Equal(s.T(), "route-machine", machine.Name)
-	assert.True(s.T(), machine.Registered)
 
 	listAllResult, err := ExecuteCommand(
 		&s.headscale,
diff --git a/machine.go b/machine.go
index dac4459..a637f54 100644
--- a/machine.go
+++ b/machine.go
@@ -2,7 +2,6 @@ package headscale
 
 import (
 	"database/sql/driver"
-	"encoding/json"
 	"fmt"
 	"sort"
 	"strconv"
@@ -13,18 +12,20 @@ import (
 	v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
 	"github.com/rs/zerolog/log"
 	"google.golang.org/protobuf/types/known/timestamppb"
-	"gorm.io/datatypes"
 	"inet.af/netaddr"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/key"
 )
 
 const (
-	errMachineNotFound            = Error("machine not found")
-	errMachineAlreadyRegistered   = Error("machine already registered")
-	errMachineRouteIsNotAvailable = Error("route is not available on machine")
-	errMachineAddressesInvalid    = Error("failed to parse machine addresses")
-	errHostnameTooLong            = Error("Hostname too long")
+	errMachineNotFound                  = Error("machine not found")
+	errMachineRouteIsNotAvailable       = Error("route is not available on machine")
+	errMachineAddressesInvalid          = Error("failed to parse machine addresses")
+	errMachineNotFoundRegistrationCache = Error(
+		"machine not found in registration cache",
+	)
+	errCouldNotConvertMachineInterface = Error("failed to convert machine interface")
+	errHostnameTooLong                 = Error("Hostname too long")
 )
 
 const (
@@ -42,18 +43,19 @@ type Machine struct {
 	NamespaceID uint
 	Namespace   Namespace `gorm:"foreignKey:NamespaceID"`
 
-	Registered     bool // temp
 	RegisterMethod string
-	AuthKeyID      uint
-	AuthKey        *PreAuthKey
+
+	// TODO(kradalby): This seems like irrelevant information?
+	AuthKeyID uint
+	AuthKey   *PreAuthKey
 
 	LastSeen             *time.Time
 	LastSuccessfulUpdate *time.Time
 	Expiry               *time.Time
 
-	HostInfo      datatypes.JSON
-	Endpoints     datatypes.JSON
-	EnabledRoutes datatypes.JSON
+	HostInfo      HostInfo
+	Endpoints     StringList
+	EnabledRoutes IPPrefixes
 
 	CreatedAt time.Time
 	UpdatedAt time.Time
@@ -65,11 +67,6 @@ type (
 	MachinesP []*Machine
 )
 
-// For the time being this method is rather naive.
-func (machine Machine) isRegistered() bool {
-	return machine.Registered
-}
-
 type MachineAddresses []netaddr.IP
 
 func (ma MachineAddresses) ToStringSlice() []string {
@@ -116,7 +113,7 @@ func (machine Machine) isExpired() bool {
 	// If Expiry is not set, the client has not indicated that
 	// it wants an expiry time, it is therefor considered
 	// to mean "not expired"
-	if machine.Expiry.IsZero() {
+	if machine.Expiry == nil || machine.Expiry.IsZero() {
 		return false
 	}
 
@@ -173,6 +170,12 @@ func getFilteredByACLPeers(
 				machine.IPAddresses.ToStringSlice(),
 				peer.IPAddresses.ToStringSlice(),
 			) || // match source and destination
+				matchSourceAndDestinationWithRule(
+					rule.SrcIPs,
+					dst,
+					peer.IPAddresses.ToStringSlice(),
+					machine.IPAddresses.ToStringSlice(),
+				) || // match return path
 				matchSourceAndDestinationWithRule(
 					rule.SrcIPs,
 					dst,
@@ -182,9 +185,21 @@ func getFilteredByACLPeers(
 				matchSourceAndDestinationWithRule(
 					rule.SrcIPs,
 					dst,
+					[]string{"*"},
+					[]string{"*"},
+				) || // match source and all destination
+				matchSourceAndDestinationWithRule(
+					rule.SrcIPs,
+					dst,
+					[]string{"*"},
 					peer.IPAddresses.ToStringSlice(),
+				) || // match source and all destination
+				matchSourceAndDestinationWithRule(
+					rule.SrcIPs,
+					dst,
+					[]string{"*"},
 					machine.IPAddresses.ToStringSlice(),
-				) { // match return path
+				) { // match all sources and source
 				peers[peer.ID] = peer
 			}
 		}
@@ -214,7 +229,7 @@ func (h *Headscale) ListPeers(machine *Machine) (Machines, error) {
 		Msg("Finding direct peers")
 
 	machines := Machines{}
-	if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Where("machine_key <> ? AND registered",
+	if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Where("machine_key <> ?",
 		machine.MachineKey).Find(&machines).Error; err != nil {
 		log.Error().Err(err).Msg("Error accessing db")
 
@@ -277,7 +292,7 @@ func (h *Headscale) getValidPeers(machine *Machine) (Machines, error) {
 	}
 
 	for _, peer := range peers {
-		if peer.isRegistered() && !peer.isExpired() {
+		if !peer.isExpired() {
 			validPeers = append(validPeers, peer)
 		}
 	}
@@ -366,8 +381,6 @@ func (h *Headscale) RefreshMachine(machine *Machine, expiry time.Time) {
 
 // DeleteMachine softs deletes a Machine from the database.
 func (h *Headscale) DeleteMachine(machine *Machine) error {
-	machine.Registered = false
-	h.db.Save(&machine) // we mark it as unregistered, just in case
 	if err := h.db.Delete(&machine).Error; err != nil {
 		return err
 	}
@@ -393,20 +406,8 @@ func (h *Headscale) HardDeleteMachine(machine *Machine) error {
 }
 
 // GetHostInfo returns a Hostinfo struct for the machine.
-func (machine *Machine) GetHostInfo() (*tailcfg.Hostinfo, error) {
-	hostinfo := tailcfg.Hostinfo{}
-	if len(machine.HostInfo) != 0 {
-		hi, err := machine.HostInfo.MarshalJSON()
-		if err != nil {
-			return nil, err
-		}
-		err = json.Unmarshal(hi, &hostinfo)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return &hostinfo, nil
+func (machine *Machine) GetHostInfo() tailcfg.Hostinfo {
+	return tailcfg.Hostinfo(machine.HostInfo)
 }
 
 func (h *Headscale) isOutdated(machine *Machine) bool {
@@ -536,54 +537,12 @@ func (machine Machine) toNode(
 	// TODO(kradalby): Needs investigation, We probably dont need this condition
 	// now that we dont have shared nodes
 	if includeRoutes {
-		routesStr := []string{}
-		if len(machine.EnabledRoutes) != 0 {
-			allwIps, err := machine.EnabledRoutes.MarshalJSON()
-			if err != nil {
-				return nil, err
-			}
-			err = json.Unmarshal(allwIps, &routesStr)
-			if err != nil {
-				return nil, err
-			}
-		}
-
-		for _, routeStr := range routesStr {
-			ip, err := netaddr.ParseIPPrefix(routeStr)
-			if err != nil {
-				return nil, err
-			}
-			allowedIPs = append(allowedIPs, ip)
-		}
-	}
-
-	endpoints := []string{}
-	if len(machine.Endpoints) != 0 {
-		be, err := machine.Endpoints.MarshalJSON()
-		if err != nil {
-			return nil, err
-		}
-		err = json.Unmarshal(be, &endpoints)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	hostinfo := tailcfg.Hostinfo{}
-	if len(machine.HostInfo) != 0 {
-		hi, err := machine.HostInfo.MarshalJSON()
-		if err != nil {
-			return nil, err
-		}
-		err = json.Unmarshal(hi, &hostinfo)
-		if err != nil {
-			return nil, err
-		}
+		allowedIPs = append(allowedIPs, machine.EnabledRoutes...)
 	}
 
 	var derp string
-	if hostinfo.NetInfo != nil {
-		derp = fmt.Sprintf("127.3.3.40:%d", hostinfo.NetInfo.PreferredDERP)
+	if machine.HostInfo.NetInfo != nil {
+		derp = fmt.Sprintf("127.3.3.40:%d", machine.HostInfo.NetInfo.PreferredDERP)
 	} else {
 		derp = "127.3.3.40:0" // Zero means disconnected or unknown.
 	}
@@ -614,6 +573,8 @@ func (machine Machine) toNode(
 		hostname = machine.Name
 	}
 
+	hostInfo := machine.GetHostInfo()
+
 	node := tailcfg.Node{
 		ID: tailcfg.NodeID(machine.ID), // this is the actual ID
 		StableID: tailcfg.StableNodeID(
@@ -627,15 +588,15 @@ func (machine Machine) toNode(
 		DiscoKey:   discoKey,
 		Addresses:  addrs,
 		AllowedIPs: allowedIPs,
-		Endpoints:  endpoints,
+		Endpoints:  machine.Endpoints,
 		DERP:       derp,
 
-		Hostinfo: hostinfo,
+		Hostinfo: hostInfo.View(),
 		Created:  machine.CreatedAt,
 		LastSeen: machine.LastSeen,
 
 		KeepAlive:         true,
-		MachineAuthorized: machine.Registered,
+		MachineAuthorized: !machine.isExpired(),
 		Capabilities:      []string{tailcfg.CapabilityFileSharing},
 	}
 
@@ -653,8 +614,6 @@ func (machine *Machine) toProto() *v1.Machine {
 		Name:        machine.Name,
 		Namespace:   machine.Namespace.toProto(),
 
-		Registered: machine.Registered,
-
 		// TODO(kradalby): Implement register method enum converter
 		// RegisterMethod: ,
 
@@ -682,74 +641,50 @@ func (machine *Machine) toProto() *v1.Machine {
 	return machineProto
 }
 
-// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
-func (h *Headscale) RegisterMachine(
+func (h *Headscale) RegisterMachineFromAuthCallback(
 	machineKeyStr string,
 	namespaceName string,
+	registrationMethod string,
 ) (*Machine, error) {
-	namespace, err := h.GetNamespace(namespaceName)
-	if err != nil {
-		return nil, err
-	}
+	if machineInterface, ok := h.registrationCache.Get(machineKeyStr); ok {
+		if registrationMachine, ok := machineInterface.(Machine); ok {
+			namespace, err := h.GetNamespace(namespaceName)
+			if err != nil {
+				return nil, fmt.Errorf(
+					"failed to find namespace in register machine from auth callback, %w",
+					err,
+				)
+			}
 
-	var machineKey key.MachinePublic
-	err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
-	if err != nil {
-		return nil, err
-	}
+			registrationMachine.NamespaceID = namespace.ID
+			registrationMachine.RegisterMethod = registrationMethod
 
-	log.Trace().
-		Caller().
-		Str("machine_key_str", machineKeyStr).
-		Str("machine_key", machineKey.String()).
-		Msg("Registering machine")
+			machine, err := h.RegisterMachine(
+				registrationMachine,
+			)
 
-	machine, err := h.GetMachineByMachineKey(machineKey)
-	if err != nil {
-		return nil, err
-	}
-
-	// TODO(kradalby): Currently, if it fails to find a requested expiry, non will be set
-	// This means that if a user is to slow with register a machine, it will possibly not
-	// have the correct expiry.
-	requestedTime := time.Time{}
-	if requestedTimeIf, found := h.requestedExpiryCache.Get(machineKey.String()); found {
-		log.Trace().
-			Caller().
-			Str("machine", machine.Name).
-			Msg("Expiry time found in cache, assigning to node")
-		if reqTime, ok := requestedTimeIf.(time.Time); ok {
-			requestedTime = reqTime
+			return machine, err
+		} else {
+			return nil, errCouldNotConvertMachineInterface
 		}
 	}
 
-	if machine.isRegistered() {
-		log.Trace().
-			Caller().
-			Str("machine", machine.Name).
-			Msg("machine already registered, reauthenticating")
+	return nil, errMachineNotFoundRegistrationCache
+}
 
-		h.RefreshMachine(machine, requestedTime)
-
-		return machine, nil
-	}
+// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
+func (h *Headscale) RegisterMachine(machine Machine,
+) (*Machine, error) {
+	log.Trace().
+		Caller().
+		Str("machine_key", machine.MachineKey).
+		Msg("Registering machine")
 
 	log.Trace().
 		Caller().
 		Str("machine", machine.Name).
 		Msg("Attempting to register machine")
 
-	if machine.isRegistered() {
-		err := errMachineAlreadyRegistered
-		log.Error().
-			Caller().
-			Err(err).
-			Str("machine", machine.Name).
-			Msg("Attempting to register machine")
-
-		return nil, err
-	}
-
 	h.ipAllocationMutex.Lock()
 	defer h.ipAllocationMutex.Unlock()
 
@@ -764,17 +699,8 @@ func (h *Headscale) RegisterMachine(
 		return nil, err
 	}
 
-	log.Trace().
-		Caller().
-		Str("machine", machine.Name).
-		Str("ip", strings.Join(ips.ToStringSlice(), ",")).
-		Msg("Found IP for host")
-
 	machine.IPAddresses = ips
-	machine.NamespaceID = namespace.ID
-	machine.Registered = true
-	machine.RegisterMethod = RegisterMethodCLI
-	machine.Expiry = &requestedTime
+
 	h.db.Save(&machine)
 
 	log.Trace().
@@ -783,40 +709,15 @@ func (h *Headscale) RegisterMachine(
 		Str("ip", strings.Join(ips.ToStringSlice(), ",")).
 		Msg("Machine registered with the database")
 
-	return machine, nil
+	return &machine, nil
 }
 
-func (machine *Machine) GetAdvertisedRoutes() ([]netaddr.IPPrefix, error) {
-	hostInfo, err := machine.GetHostInfo()
-	if err != nil {
-		return nil, err
-	}
-
-	return hostInfo.RoutableIPs, nil
+func (machine *Machine) GetAdvertisedRoutes() []netaddr.IPPrefix {
+	return machine.HostInfo.RoutableIPs
 }
 
-func (machine *Machine) GetEnabledRoutes() ([]netaddr.IPPrefix, error) {
-	data, err := machine.EnabledRoutes.MarshalJSON()
-	if err != nil {
-		return nil, err
-	}
-
-	routesStr := []string{}
-	err = json.Unmarshal(data, &routesStr)
-	if err != nil {
-		return nil, err
-	}
-
-	routes := make([]netaddr.IPPrefix, len(routesStr))
-	for index, routeStr := range routesStr {
-		route, err := netaddr.ParseIPPrefix(routeStr)
-		if err != nil {
-			return nil, err
-		}
-		routes[index] = route
-	}
-
-	return routes, nil
+func (machine *Machine) GetEnabledRoutes() []netaddr.IPPrefix {
+	return machine.EnabledRoutes
 }
 
 func (machine *Machine) IsRoutesEnabled(routeStr string) bool {
@@ -825,10 +726,7 @@ func (machine *Machine) IsRoutesEnabled(routeStr string) bool {
 		return false
 	}
 
-	enabledRoutes, err := machine.GetEnabledRoutes()
-	if err != nil {
-		return false
-	}
+	enabledRoutes := machine.GetEnabledRoutes()
 
 	for _, enabledRoute := range enabledRoutes {
 		if route == enabledRoute {
@@ -852,13 +750,8 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
 		newRoutes[index] = route
 	}
 
-	availableRoutes, err := machine.GetAdvertisedRoutes()
-	if err != nil {
-		return err
-	}
-
 	for _, newRoute := range newRoutes {
-		if !containsIPPrefix(availableRoutes, newRoute) {
+		if !containsIPPrefix(machine.GetAdvertisedRoutes(), newRoute) {
 			return fmt.Errorf(
 				"route (%s) is not available on node %s: %w",
 				machine.Name,
@@ -867,30 +760,19 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
 		}
 	}
 
-	routes, err := json.Marshal(newRoutes)
-	if err != nil {
-		return err
-	}
-
-	machine.EnabledRoutes = datatypes.JSON(routes)
+	machine.EnabledRoutes = newRoutes
 	h.db.Save(&machine)
 
 	return nil
 }
 
-func (machine *Machine) RoutesToProto() (*v1.Routes, error) {
-	availableRoutes, err := machine.GetAdvertisedRoutes()
-	if err != nil {
-		return nil, err
-	}
+func (machine *Machine) RoutesToProto() *v1.Routes {
+	availableRoutes := machine.GetAdvertisedRoutes()
 
-	enabledRoutes, err := machine.GetEnabledRoutes()
-	if err != nil {
-		return nil, err
-	}
+	enabledRoutes := machine.GetEnabledRoutes()
 
 	return &v1.Routes{
 		AdvertisedRoutes: ipPrefixToString(availableRoutes),
 		EnabledRoutes:    ipPrefixToString(enabledRoutes),
-	}, nil
+	}
 }
diff --git a/machine_test.go b/machine_test.go
index e9c91f8..a455a0b 100644
--- a/machine_test.go
+++ b/machine_test.go
@@ -29,16 +29,12 @@ func (s *Suite) TestGetMachine(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 	}
 	app.db.Save(machine)
 
-	machineFromDB, err := app.GetMachine("test", "testmachine")
-	c.Assert(err, check.IsNil)
-
-	_, err = machineFromDB.GetHostInfo()
+	_, err = app.GetMachine("test", "testmachine")
 	c.Assert(err, check.IsNil)
 }
 
@@ -59,16 +55,12 @@ func (s *Suite) TestGetMachineByID(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 	}
 	app.db.Save(&machine)
 
-	machineByID, err := app.GetMachineByID(0)
-	c.Assert(err, check.IsNil)
-
-	_, err = machineByID.GetHostInfo()
+	_, err = app.GetMachineByID(0)
 	c.Assert(err, check.IsNil)
 }
 
@@ -82,7 +74,6 @@ func (s *Suite) TestDeleteMachine(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(1),
 	}
@@ -105,7 +96,6 @@ func (s *Suite) TestHardDeleteMachine(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine3",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(1),
 	}
@@ -136,7 +126,6 @@ func (s *Suite) TestListPeers(c *check.C) {
 			DiscoKey:       "faa" + strconv.Itoa(index),
 			Name:           "testmachine" + strconv.Itoa(index),
 			NamespaceID:    namespace.ID,
-			Registered:     true,
 			RegisterMethod: RegisterMethodAuthKey,
 			AuthKeyID:      uint(pak.ID),
 		}
@@ -146,9 +135,6 @@ func (s *Suite) TestListPeers(c *check.C) {
 	machine0ByID, err := app.GetMachineByID(0)
 	c.Assert(err, check.IsNil)
 
-	_, err = machine0ByID.GetHostInfo()
-	c.Assert(err, check.IsNil)
-
 	peersOfMachine0, err := app.ListPeers(machine0ByID)
 	c.Assert(err, check.IsNil)
 
@@ -188,7 +174,6 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
 			},
 			Name:           "testmachine" + strconv.Itoa(index),
 			NamespaceID:    stor[index%2].namespace.ID,
-			Registered:     true,
 			RegisterMethod: RegisterMethodAuthKey,
 			AuthKeyID:      uint(stor[index%2].key.ID),
 		}
@@ -219,9 +204,6 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
 	c.Logf("Machine(%v), namespace: %v", testMachine.Name, testMachine.Namespace)
 	c.Assert(err, check.IsNil)
 
-	_, err = testMachine.GetHostInfo()
-	c.Assert(err, check.IsNil)
-
 	machines, err := app.ListMachines()
 	c.Assert(err, check.IsNil)
 
@@ -258,7 +240,6 @@ func (s *Suite) TestExpireMachine(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 		Expiry:         &time.Time{},
@@ -296,6 +277,7 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
 	}
 }
 
+// nolint
 func Test_getFilteredByACLPeers(t *testing.T) {
 	type args struct {
 		machines []Machine
@@ -443,7 +425,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
 					},
 				},
 				machine: &Machine{ // current machine
-					ID:          1,
+					ID:          2,
 					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
 					Namespace:   Namespace{Name: "marc"},
 				},
@@ -456,6 +438,208 @@ func Test_getFilteredByACLPeers(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "rules allows all hosts to reach one destination",
+			args: args{
+				machines: []Machine{ // list of all machines in the database
+					{
+						ID: 1,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.1"),
+						},
+						Namespace: Namespace{Name: "joe"},
+					},
+					{
+						ID: 2,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.2"),
+						},
+						Namespace: Namespace{Name: "marc"},
+					},
+					{
+						ID: 3,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.3"),
+						},
+						Namespace: Namespace{Name: "mickael"},
+					},
+				},
+				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
+					{
+						SrcIPs: []string{"*"},
+						DstPorts: []tailcfg.NetPortRange{
+							{IP: "100.64.0.2"},
+						},
+					},
+				},
+				machine: &Machine{ // current machine
+					ID: 1,
+					IPAddresses: MachineAddresses{
+						netaddr.MustParseIP("100.64.0.1"),
+					},
+					Namespace: Namespace{Name: "joe"},
+				},
+			},
+			want: Machines{
+				{
+					ID: 2,
+					IPAddresses: MachineAddresses{
+						netaddr.MustParseIP("100.64.0.2"),
+					},
+					Namespace: Namespace{Name: "marc"},
+				},
+			},
+		},
+		{
+			name: "rules allows all hosts to reach one destination, destination can reach all hosts",
+			args: args{
+				machines: []Machine{ // list of all machines in the database
+					{
+						ID: 1,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.1"),
+						},
+						Namespace: Namespace{Name: "joe"},
+					},
+					{
+						ID: 2,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.2"),
+						},
+						Namespace: Namespace{Name: "marc"},
+					},
+					{
+						ID: 3,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.3"),
+						},
+						Namespace: Namespace{Name: "mickael"},
+					},
+				},
+				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
+					{
+						SrcIPs: []string{"*"},
+						DstPorts: []tailcfg.NetPortRange{
+							{IP: "100.64.0.2"},
+						},
+					},
+				},
+				machine: &Machine{ // current machine
+					ID: 2,
+					IPAddresses: MachineAddresses{
+						netaddr.MustParseIP("100.64.0.2"),
+					},
+					Namespace: Namespace{Name: "marc"},
+				},
+			},
+			want: Machines{
+				{
+					ID: 1,
+					IPAddresses: MachineAddresses{
+						netaddr.MustParseIP("100.64.0.1"),
+					},
+					Namespace: Namespace{Name: "joe"},
+				},
+				{
+					ID: 3,
+					IPAddresses: MachineAddresses{
+						netaddr.MustParseIP("100.64.0.3"),
+					},
+					Namespace: Namespace{Name: "mickael"},
+				},
+			},
+		},
+		{
+			name: "rule allows all hosts to reach all destinations",
+			args: args{
+				machines: []Machine{ // list of all machines in the database
+					{
+						ID: 1,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.1"),
+						},
+						Namespace: Namespace{Name: "joe"},
+					},
+					{
+						ID: 2,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.2"),
+						},
+						Namespace: Namespace{Name: "marc"},
+					},
+					{
+						ID: 3,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.3"),
+						},
+						Namespace: Namespace{Name: "mickael"},
+					},
+				},
+				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
+					{
+						SrcIPs: []string{"*"},
+						DstPorts: []tailcfg.NetPortRange{
+							{IP: "*"},
+						},
+					},
+				},
+				machine: &Machine{ // current machine
+					ID:          2,
+					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
+					Namespace:   Namespace{Name: "marc"},
+				},
+			},
+			want: Machines{
+				{
+					ID: 1,
+					IPAddresses: MachineAddresses{
+						netaddr.MustParseIP("100.64.0.1"),
+					},
+					Namespace: Namespace{Name: "joe"},
+				},
+				{
+					ID:          3,
+					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
+					Namespace:   Namespace{Name: "mickael"},
+				},
+			},
+		},
+		{
+			name: "without rule all communications are forbidden",
+			args: args{
+				machines: []Machine{ // list of all machines in the database
+					{
+						ID: 1,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.1"),
+						},
+						Namespace: Namespace{Name: "joe"},
+					},
+					{
+						ID: 2,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.2"),
+						},
+						Namespace: Namespace{Name: "marc"},
+					},
+					{
+						ID: 3,
+						IPAddresses: MachineAddresses{
+							netaddr.MustParseIP("100.64.0.3"),
+						},
+						Namespace: Namespace{Name: "mickael"},
+					},
+				},
+				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
+				},
+				machine: &Machine{ // current machine
+					ID:          2,
+					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
+					Namespace:   Namespace{Name: "marc"},
+				},
+			},
+			want: Machines{},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/namespaces_test.go b/namespaces_test.go
index 585ba93..1710f7d 100644
--- a/namespaces_test.go
+++ b/namespaces_test.go
@@ -54,7 +54,6 @@ func (s *Suite) TestDestroyNamespaceErrors(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 	}
@@ -146,7 +145,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 		Name:           "test_get_shared_nodes_1",
 		NamespaceID:    namespaceShared1.ID,
 		Namespace:      *namespaceShared1,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
 		AuthKeyID:      uint(preAuthKeyShared1.ID),
@@ -164,7 +162,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 		Name:           "test_get_shared_nodes_2",
 		NamespaceID:    namespaceShared2.ID,
 		Namespace:      *namespaceShared2,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
 		AuthKeyID:      uint(preAuthKeyShared2.ID),
@@ -182,7 +179,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 		Name:           "test_get_shared_nodes_3",
 		NamespaceID:    namespaceShared3.ID,
 		Namespace:      *namespaceShared3,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
 		AuthKeyID:      uint(preAuthKeyShared3.ID),
@@ -200,7 +196,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
 		Name:           "test_get_shared_nodes_4",
 		NamespaceID:    namespaceShared1.ID,
 		Namespace:      *namespaceShared1,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
 		AuthKeyID:      uint(preAuthKey2Shared1.ID),
diff --git a/oidc.go b/oidc.go
index edea32a..987bc8b 100644
--- a/oidc.go
+++ b/oidc.go
@@ -10,21 +10,16 @@ import (
 	"html/template"
 	"net/http"
 	"strings"
-	"time"
 
 	"github.com/coreos/go-oidc/v3/oidc"
 	"github.com/gin-gonic/gin"
-	"github.com/patrickmn/go-cache"
 	"github.com/rs/zerolog/log"
 	"golang.org/x/oauth2"
-	"gorm.io/gorm"
 	"tailscale.com/types/key"
 )
 
 const (
-	oidcStateCacheExpiration      = time.Minute * 5
-	oidcStateCacheCleanupInterval = time.Minute * 10
-	randomByteSize                = 16
+	randomByteSize = 16
 )
 
 type IDTokenClaims struct {
@@ -61,14 +56,6 @@ func (h *Headscale) initOIDC() error {
 		}
 	}
 
-	// init the state cache if it hasn't been already
-	if h.oidcStateCache == nil {
-		h.oidcStateCache = cache.New(
-			oidcStateCacheExpiration,
-			oidcStateCacheCleanupInterval,
-		)
-	}
-
 	return nil
 }
 
@@ -101,7 +88,7 @@ func (h *Headscale) RegisterOIDC(ctx *gin.Context) {
 	stateStr := hex.EncodeToString(randomBlob)[:32]
 
 	// place the machine key into the state cache, so it can be retrieved later
-	h.oidcStateCache.Set(stateStr, machineKeyStr, oidcStateCacheExpiration)
+	h.registrationCache.Set(stateStr, machineKeyStr, registerCacheExpiration)
 
 	authURL := h.oauth2Config.AuthCodeURL(stateStr)
 	log.Debug().Msgf("Redirecting to %s for authentication", authURL)
@@ -125,7 +112,6 @@ var oidcCallbackTemplate = template.Must(
 	</html>`),
 )
 
-// TODO: Why is the entire machine registration logic duplicated here?
 // OIDCCallback handles the callback from the OIDC endpoint
 // Retrieves the mkey from the state cache and adds the machine to the users email namespace
 // TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
@@ -197,7 +183,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
 	}
 
 	// retrieve machinekey from state cache
-	machineKeyIf, machineKeyFound := h.oidcStateCache.Get(state)
+	machineKeyIf, machineKeyFound := h.registrationCache.Get(state)
 
 	if !machineKeyFound {
 		log.Error().
@@ -207,10 +193,12 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
 		return
 	}
 
-	machineKeyStr, machineKeyOK := machineKeyIf.(string)
+	machineKeyFromCache, machineKeyOK := machineKeyIf.(string)
 
 	var machineKey key.MachinePublic
-	err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
+	err = machineKey.UnmarshalText(
+		[]byte(MachinePublicKeyEnsurePrefix(machineKeyFromCache)),
+	)
 	if err != nil {
 		log.Error().
 			Msg("could not parse machine public key")
@@ -229,33 +217,19 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
 		return
 	}
 
-	// TODO(kradalby): Currently, if it fails to find a requested expiry, non will be set
-	requestedTime := time.Time{}
-	if requestedTimeIf, found := h.requestedExpiryCache.Get(machineKey.String()); found {
-		if reqTime, ok := requestedTimeIf.(time.Time); ok {
-			requestedTime = reqTime
-		}
-	}
+	// retrieve machine information if it exist
+	// The error is not important, because if it does not
+	// exist, then this is a new machine and we will move
+	// on to registration.
+	machine, _ := h.GetMachineByMachineKey(machineKey)
 
-	// retrieve machine information
-	machine, err := h.GetMachineByMachineKey(machineKey)
-	if err != nil {
-		log.Error().Msg("machine key not found in database")
-		ctx.String(
-			http.StatusInternalServerError,
-			"could not get machine info from database",
-		)
-
-		return
-	}
-
-	if machine.isRegistered() {
+	if machine != nil {
 		log.Trace().
 			Caller().
 			Str("machine", machine.Name).
 			Msg("machine already registered, reauthenticating")
 
-		h.RefreshMachine(machine, requestedTime)
+		h.RefreshMachine(machine, *machine.Expiry)
 
 		var content bytes.Buffer
 		if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
@@ -279,8 +253,6 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
 		return
 	}
 
-	now := time.Now().UTC()
-
 	namespaceName, err := NormalizeNamespaceName(
 		claims.Email,
 		h.cfg.OIDC.StripEmaildomain,
@@ -294,61 +266,58 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
 
 		return
 	}
+
 	// register the machine if it's new
-	if !machine.Registered {
-		log.Debug().Msg("Registering new machine after successful callback")
+	log.Debug().Msg("Registering new machine after successful callback")
 
-		namespace, err := h.GetNamespace(namespaceName)
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			namespace, err = h.CreateNamespace(namespaceName)
+	namespace, err := h.GetNamespace(namespaceName)
+	if errors.Is(err, errNamespaceNotFound) {
+		namespace, err = h.CreateNamespace(namespaceName)
 
-			if err != nil {
-				log.Error().
-					Err(err).
-					Caller().
-					Msgf("could not create new namespace '%s'", namespaceName)
-				ctx.String(
-					http.StatusInternalServerError,
-					"could not create new namespace",
-				)
-
-				return
-			}
-		} else if err != nil {
-			log.Error().
-				Caller().
-				Err(err).
-				Str("namespace", namespaceName).
-				Msg("could not find or create namespace")
-			ctx.String(
-				http.StatusInternalServerError,
-				"could not find or create namespace",
-			)
-
-			return
-		}
-
-		ips, err := h.getAvailableIPs()
 		if err != nil {
 			log.Error().
-				Caller().
 				Err(err).
-				Msg("could not get an IP from the pool")
+				Caller().
+				Msgf("could not create new namespace '%s'", namespaceName)
 			ctx.String(
 				http.StatusInternalServerError,
-				"could not get an IP from the pool",
+				"could not create new namespace",
 			)
 
 			return
 		}
+	} else if err != nil {
+		log.Error().
+			Caller().
+			Err(err).
+			Str("namespace", namespaceName).
+			Msg("could not find or create namespace")
+		ctx.String(
+			http.StatusInternalServerError,
+			"could not find or create namespace",
+		)
 
-		machine.IPAddresses = ips
-		machine.NamespaceID = namespace.ID
-		machine.Registered = true
-		machine.RegisterMethod = RegisterMethodOIDC
-		machine.LastSuccessfulUpdate = &now
-		machine.Expiry = &requestedTime
-		h.db.Save(&machine)
+		return
+	}
+
+	machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
+
+	_, err = h.RegisterMachineFromAuthCallback(
+		machineKeyStr,
+		namespace.Name,
+		RegisterMethodOIDC,
+	)
+	if err != nil {
+		log.Error().
+			Caller().
+			Err(err).
+			Msg("could not register machine")
+		ctx.String(
+			http.StatusInternalServerError,
+			"could not register machine",
+		)
+
+		return
 	}
 
 	var content bytes.Buffer
diff --git a/poll.go b/poll.go
index 21aa3b3..1ae8cd0 100644
--- a/poll.go
+++ b/poll.go
@@ -2,7 +2,6 @@ package headscale
 
 import (
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -11,7 +10,6 @@ import (
 
 	"github.com/gin-gonic/gin"
 	"github.com/rs/zerolog/log"
-	"gorm.io/datatypes"
 	"gorm.io/gorm"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/key"
@@ -85,12 +83,8 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 		Str("machine", machine.Name).
 		Msg("Found machine in database")
 
-	hostinfo, err := json.Marshal(req.Hostinfo)
-	if err != nil {
-		return
-	}
 	machine.Name = req.Hostinfo.Hostname
-	machine.HostInfo = datatypes.JSON(hostinfo)
+	machine.HostInfo = HostInfo(*req.Hostinfo)
 	machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey)
 	now := time.Now().UTC()
 
@@ -114,18 +108,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 	// The intended use is for clients to discover the DERP map at start-up
 	// before their first real endpoint update.
 	if !req.ReadOnly {
-		endpoints, err := json.Marshal(req.Endpoints)
-		if err != nil {
-			log.Error().
-				Caller().
-				Str("func", "PollNetMapHandler").
-				Err(err).
-				Msg("Failed to mashal requested endpoints for the client")
-			ctx.String(http.StatusInternalServerError, ":(")
-
-			return
-		}
-		machine.Endpoints = datatypes.JSON(endpoints)
+		machine.Endpoints = req.Endpoints
 		machine.LastSeen = &now
 	}
 	h.db.Updates(machine)
diff --git a/preauth_keys.go b/preauth_keys.go
index 50bc474..55f6222 100644
--- a/preauth_keys.go
+++ b/preauth_keys.go
@@ -113,6 +113,12 @@ func (h *Headscale) ExpirePreAuthKey(k *PreAuthKey) error {
 	return nil
 }
 
+// UsePreAuthKey marks a PreAuthKey as used.
+func (h *Headscale) UsePreAuthKey(k *PreAuthKey) {
+	k.Used = true
+	h.db.Save(k)
+}
+
 // checkKeyValidity does the heavy lifting for validation of the PreAuthKey coming from a node
 // If returns no error and a PreAuthKey, it can be used.
 func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
diff --git a/preauth_keys_test.go b/preauth_keys_test.go
index f8cf276..6f233a9 100644
--- a/preauth_keys_test.go
+++ b/preauth_keys_test.go
@@ -80,7 +80,6 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testest",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 	}
@@ -105,7 +104,6 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testest",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 	}
@@ -143,7 +141,6 @@ func (*Suite) TestEphemeralKey(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testest",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		LastSeen:       &now,
 		AuthKeyID:      uint(pak.ID),
diff --git a/proto/headscale/v1/machine.proto b/proto/headscale/v1/machine.proto
index 7451db8..75552d7 100644
--- a/proto/headscale/v1/machine.proto
+++ b/proto/headscale/v1/machine.proto
@@ -22,16 +22,16 @@ message Machine {
     string          name         = 6;
     Namespace namespace          = 7;
 
-    bool           registered      = 8;
-    RegisterMethod register_method = 9;
 
-    google.protobuf.Timestamp last_seen              = 10;
-    google.protobuf.Timestamp last_successful_update = 11;
-    google.protobuf.Timestamp expiry                 = 12;
+    google.protobuf.Timestamp last_seen              = 8;
+    google.protobuf.Timestamp last_successful_update = 9;
+    google.protobuf.Timestamp expiry                 = 10;
 
-    PreAuthKey pre_auth_key = 13;
+    PreAuthKey pre_auth_key = 11;
 
-    google.protobuf.Timestamp created_at = 14;
+    google.protobuf.Timestamp created_at = 12;
+
+    RegisterMethod register_method = 13;
     // google.protobuf.Timestamp updated_at = 14;
     // google.protobuf.Timestamp deleted_at = 15;
 
diff --git a/routes.go b/routes.go
index 0065a03..e8de299 100644
--- a/routes.go
+++ b/routes.go
@@ -1,9 +1,6 @@
 package headscale
 
 import (
-	"encoding/json"
-
-	"gorm.io/datatypes"
 	"inet.af/netaddr"
 )
 
@@ -23,12 +20,7 @@ func (h *Headscale) GetAdvertisedNodeRoutes(
 		return nil, err
 	}
 
-	hostInfo, err := machine.GetHostInfo()
-	if err != nil {
-		return nil, err
-	}
-
-	return &hostInfo.RoutableIPs, nil
+	return &machine.HostInfo.RoutableIPs, nil
 }
 
 // Deprecated: use machine function instead
@@ -43,27 +35,7 @@ func (h *Headscale) GetEnabledNodeRoutes(
 		return nil, err
 	}
 
-	data, err := machine.EnabledRoutes.MarshalJSON()
-	if err != nil {
-		return nil, err
-	}
-
-	routesStr := []string{}
-	err = json.Unmarshal(data, &routesStr)
-	if err != nil {
-		return nil, err
-	}
-
-	routes := make([]netaddr.IPPrefix, len(routesStr))
-	for index, routeStr := range routesStr {
-		route, err := netaddr.ParseIPPrefix(routeStr)
-		if err != nil {
-			return nil, err
-		}
-		routes[index] = route
-	}
-
-	return routes, nil
+	return machine.EnabledRoutes, nil
 }
 
 // Deprecated: use machine function instead
@@ -135,12 +107,7 @@ func (h *Headscale) EnableNodeRoute(
 		return errRouteIsNotAvailable
 	}
 
-	routes, err := json.Marshal(enabledRoutes)
-	if err != nil {
-		return err
-	}
-
-	machine.EnabledRoutes = datatypes.JSON(routes)
+	machine.EnabledRoutes = enabledRoutes
 	h.db.Save(&machine)
 
 	return nil
diff --git a/routes_test.go b/routes_test.go
index 94bda45..31b8d44 100644
--- a/routes_test.go
+++ b/routes_test.go
@@ -1,10 +1,7 @@
 package headscale
 
 import (
-	"encoding/json"
-
 	"gopkg.in/check.v1"
-	"gorm.io/datatypes"
 	"inet.af/netaddr"
 	"tailscale.com/tailcfg"
 )
@@ -25,8 +22,6 @@ func (s *Suite) TestGetRoutes(c *check.C) {
 	hostInfo := tailcfg.Hostinfo{
 		RoutableIPs: []netaddr.IPPrefix{route},
 	}
-	hostinfo, err := json.Marshal(hostInfo)
-	c.Assert(err, check.IsNil)
 
 	machine := Machine{
 		ID:             0,
@@ -35,10 +30,9 @@ func (s *Suite) TestGetRoutes(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "test_get_route_machine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
-		HostInfo:       datatypes.JSON(hostinfo),
+		HostInfo:       HostInfo(hostInfo),
 	}
 	app.db.Save(&machine)
 
@@ -79,8 +73,6 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
 	hostInfo := tailcfg.Hostinfo{
 		RoutableIPs: []netaddr.IPPrefix{route, route2},
 	}
-	hostinfo, err := json.Marshal(hostInfo)
-	c.Assert(err, check.IsNil)
 
 	machine := Machine{
 		ID:             0,
@@ -89,10 +81,9 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "test_enable_route_machine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
-		HostInfo:       datatypes.JSON(hostinfo),
+		HostInfo:       HostInfo(hostInfo),
 	}
 	app.db.Save(&machine)
 
diff --git a/tests/acls/acl_policy_basic_wildcards.yaml b/tests/acls/acl_policy_basic_wildcards.yaml
new file mode 100644
index 0000000..8e7c817
--- /dev/null
+++ b/tests/acls/acl_policy_basic_wildcards.yaml
@@ -0,0 +1,10 @@
+---
+Hosts:
+  host-1: 100.100.100.100/32
+  subnet-1: 100.100.101.100/24
+ACLs:
+  - Action: accept
+    Users:
+      - "*"
+    Ports:
+      - host-1:*
diff --git a/utils.go b/utils.go
index 004bf30..af267eb 100644
--- a/utils.go
+++ b/utils.go
@@ -196,10 +196,6 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) {
 	var addressesSlices []string
 	h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
 
-	log.Trace().
-		Strs("addresses", addressesSlices).
-		Msg("Got allocated ip addresses from databases")
-
 	var ips netaddr.IPSetBuilder
 	for _, slice := range addressesSlices {
 		var machineAddresses MachineAddresses
@@ -216,10 +212,6 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) {
 		}
 	}
 
-	log.Trace().
-		Interface("addresses", ips).
-		Msg("Parsed ip addresses that has been allocated from databases")
-
 	ipSet, err := ips.IPSet()
 	if err != nil {
 		return &netaddr.IPSet{}, fmt.Errorf(
diff --git a/utils_test.go b/utils_test.go
index 271013a..7c72c09 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -36,7 +36,6 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 		IPAddresses:    ips,
@@ -85,7 +84,6 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
 			DiscoKey:       "faa",
 			Name:           "testmachine",
 			NamespaceID:    namespace.ID,
-			Registered:     true,
 			RegisterMethod: RegisterMethodAuthKey,
 			AuthKeyID:      uint(pak.ID),
 			IPAddresses:    ips,
@@ -176,7 +174,6 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
 		DiscoKey:       "faa",
 		Name:           "testmachine",
 		NamespaceID:    namespace.ID,
-		Registered:     true,
 		RegisterMethod: RegisterMethodAuthKey,
 		AuthKeyID:      uint(pak.ID),
 	}