mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-24 13:46:53 +02:00
Added manual approval of nodes in the network
This commit is contained in:
parent
89f70595d1
commit
122d86f2fa
@ -48,6 +48,13 @@ func init() {
|
||||
}
|
||||
nodeCmd.AddCommand(registerNodeCmd)
|
||||
|
||||
approveNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||
err = approveNodeCmd.MarkFlagRequired("identifier")
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
nodeCmd.AddCommand(approveNodeCmd)
|
||||
|
||||
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||
err = expireNodeCmd.MarkFlagRequired("identifier")
|
||||
if err != nil {
|
||||
@ -206,6 +213,43 @@ var listNodesCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var approveNodeCmd = &cobra.Command{
|
||||
Use: "approve",
|
||||
Short: "Approve a node in your network",
|
||||
Aliases: []string{"a"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
identifier, err := cmd.Flags().GetUint64("identifier")
|
||||
if err != nil {
|
||||
ErrorOutput(
|
||||
err,
|
||||
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||
output,
|
||||
)
|
||||
return
|
||||
}
|
||||
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
|
||||
defer cancel()
|
||||
defer conn.Close()
|
||||
request := &v1.ApproveNodeRequest{
|
||||
NodeId: identifier,
|
||||
}
|
||||
response, err := client.ApproveNode(ctx, request)
|
||||
if err != nil {
|
||||
ErrorOutput(
|
||||
err,
|
||||
fmt.Sprintf(
|
||||
"Cannot expire node: %s\n",
|
||||
status.Convert(err).Message(),
|
||||
),
|
||||
output,
|
||||
)
|
||||
return
|
||||
}
|
||||
SuccessOutput(response.GetNode(), "Node approved", output)
|
||||
},
|
||||
}
|
||||
|
||||
var expireNodeCmd = &cobra.Command{
|
||||
Use: "expire",
|
||||
Short: "Expire (log out) a node in your network",
|
||||
|
@ -36,6 +36,8 @@ func init() {
|
||||
preauthkeysCmd.AddCommand(expirePreAuthKeyCmd)
|
||||
createPreAuthKeyCmd.PersistentFlags().
|
||||
Bool("reusable", false, "Make the preauthkey reusable")
|
||||
createPreAuthKeyCmd.PersistentFlags().
|
||||
Bool("pre-approved", false, "Make the preauthkey with node pre-approval")
|
||||
createPreAuthKeyCmd.PersistentFlags().
|
||||
Bool("ephemeral", false, "Preauthkey for ephemeral nodes")
|
||||
createPreAuthKeyCmd.Flags().
|
||||
@ -90,6 +92,7 @@ var listPreAuthKeys = &cobra.Command{
|
||||
"ID",
|
||||
"Key",
|
||||
"Reusable",
|
||||
"Pre-Approved",
|
||||
"Ephemeral",
|
||||
"Used",
|
||||
"Expiration",
|
||||
@ -115,6 +118,7 @@ var listPreAuthKeys = &cobra.Command{
|
||||
key.GetId(),
|
||||
key.GetKey(),
|
||||
strconv.FormatBool(key.GetReusable()),
|
||||
strconv.FormatBool(key.GetPreApproved()),
|
||||
strconv.FormatBool(key.GetEphemeral()),
|
||||
strconv.FormatBool(key.GetUsed()),
|
||||
expiration,
|
||||
@ -147,14 +151,16 @@ var createPreAuthKeyCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
reusable, _ := cmd.Flags().GetBool("reusable")
|
||||
preApproved, _ := cmd.Flags().GetBool("pre-approved")
|
||||
ephemeral, _ := cmd.Flags().GetBool("ephemeral")
|
||||
tags, _ := cmd.Flags().GetStringSlice("tags")
|
||||
|
||||
request := &v1.CreatePreAuthKeyRequest{
|
||||
User: user,
|
||||
Reusable: reusable,
|
||||
Ephemeral: ephemeral,
|
||||
AclTags: tags,
|
||||
User: user,
|
||||
Reusable: reusable,
|
||||
PreApproved: preApproved,
|
||||
Ephemeral: ephemeral,
|
||||
AclTags: tags,
|
||||
}
|
||||
|
||||
durationStr, _ := cmd.Flags().GetString("expiration")
|
||||
|
@ -395,3 +395,9 @@ logtail:
|
||||
# default static port 41641. This option is intended as a workaround for some buggy
|
||||
# firewall devices. See https://tailscale.com/kb/1181/firewalls/ for more information.
|
||||
randomize_client_port: false
|
||||
|
||||
# Node management
|
||||
# See https://tailscale.com/kb/1099/device-approval for more information.
|
||||
node_management:
|
||||
# Require new nodes to be approved by admins before they can access the network.
|
||||
manual_approve_new_node: false
|
@ -137,6 +137,7 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
|
||||
app.db, err = db.NewHeadscaleDatabase(
|
||||
cfg.Database,
|
||||
cfg.BaseDomain,
|
||||
cfg.NodeManagement,
|
||||
registrationCache,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -292,6 +292,11 @@ func (h *Headscale) handleAuthKey(
|
||||
|
||||
nodeKey := registerRequest.NodeKey
|
||||
|
||||
nodeApproved := true
|
||||
if h.cfg.NodeManagement.ManualApproveNewNode {
|
||||
nodeApproved = pak.PreApproved
|
||||
}
|
||||
|
||||
// retrieve node information if it exist
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new node and we will move
|
||||
@ -308,6 +313,10 @@ func (h *Headscale) handleAuthKey(
|
||||
node.AuthKeyID = ptr.To(pak.ID)
|
||||
}
|
||||
|
||||
if node.Approved == false {
|
||||
node.Approved = nodeApproved
|
||||
}
|
||||
|
||||
node.Expiry = ®isterRequest.Expiry
|
||||
node.User = pak.User
|
||||
node.UserID = pak.UserID
|
||||
@ -349,6 +358,7 @@ func (h *Headscale) handleAuthKey(
|
||||
User: pak.User,
|
||||
MachineKey: machineKey,
|
||||
RegisterMethod: util.RegisterMethodAuthKey,
|
||||
Approved: nodeApproved,
|
||||
Expiry: ®isterRequest.Expiry,
|
||||
NodeKey: nodeKey,
|
||||
LastSeen: &now,
|
||||
@ -406,7 +416,7 @@ func (h *Headscale) handleAuthKey(
|
||||
return
|
||||
}
|
||||
|
||||
resp.MachineAuthorized = true
|
||||
resp.MachineAuthorized = node.IsApproved()
|
||||
resp.User = *pak.User.TailscaleUser()
|
||||
// Provide LoginName when registering with pre-auth key
|
||||
// Otherwise it will need to exec `tailscale up` twice to fetch the *LoginName*
|
||||
@ -569,7 +579,7 @@ func (h *Headscale) handleNodeWithValidRegistration(
|
||||
Msg("Client is registered and we have the current NodeKey. All clear to /map")
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.MachineAuthorized = true
|
||||
resp.MachineAuthorized = node.IsApproved()
|
||||
resp.User = *node.User.TailscaleUser()
|
||||
resp.Login = *node.User.TailscaleLogin()
|
||||
|
||||
|
@ -43,7 +43,8 @@ type HSDatabase struct {
|
||||
cfg *types.DatabaseConfig
|
||||
regCache *zcache.Cache[string, types.Node]
|
||||
|
||||
baseDomain string
|
||||
baseDomain string
|
||||
nodeManagement *types.NodeManagement
|
||||
}
|
||||
|
||||
// TODO(kradalby): assemble this struct from toptions or something typed
|
||||
@ -51,6 +52,7 @@ type HSDatabase struct {
|
||||
func NewHeadscaleDatabase(
|
||||
cfg types.DatabaseConfig,
|
||||
baseDomain string,
|
||||
nodeManagement types.NodeManagement,
|
||||
regCache *zcache.Cache[string, types.Node],
|
||||
) (*HSDatabase, error) {
|
||||
dbConn, err := openDB(cfg)
|
||||
@ -521,6 +523,48 @@ func NewHeadscaleDatabase(
|
||||
},
|
||||
Rollback: func(db *gorm.DB) error { return nil },
|
||||
},
|
||||
{
|
||||
ID: "202410071005",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
err = tx.AutoMigrate(&types.PreAuthKey{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.AutoMigrate(&types.Node{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tx.Migrator().HasColumn(&types.Node{}, "approved") {
|
||||
nodes := types.Nodes{}
|
||||
if err := tx.Find(&nodes).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error accessing db")
|
||||
}
|
||||
|
||||
for item, node := range nodes {
|
||||
if node.IsApproved() == false {
|
||||
err = tx.Model(nodes[item]).Updates(types.Node{
|
||||
Approved: true,
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("hostname", node.Hostname).
|
||||
Bool("approved", node.IsApproved()).
|
||||
Err(err).
|
||||
Msg("Failed to add approval option to existing nodes during database migration")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("no node approved column in DB")
|
||||
},
|
||||
Rollback: func(db *gorm.DB) error { return nil },
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@ -533,7 +577,8 @@ func NewHeadscaleDatabase(
|
||||
cfg: &cfg,
|
||||
regCache: regCache,
|
||||
|
||||
baseDomain: baseDomain,
|
||||
baseDomain: baseDomain,
|
||||
nodeManagement: &nodeManagement,
|
||||
}
|
||||
|
||||
return &db, err
|
||||
|
@ -265,10 +265,10 @@ func isTailscaleReservedIP(ip netip.Addr) bool {
|
||||
// it will be added.
|
||||
// If a prefix type has been removed (IPv4 or IPv6), it
|
||||
// will remove the IPs in that family from the node.
|
||||
func (db *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) {
|
||||
func (hsdb *HSDatabase) BackfillNodeIPs(i *IPAllocator) ([]string, error) {
|
||||
var err error
|
||||
var ret []string
|
||||
err = db.Write(func(tx *gorm.DB) error {
|
||||
err = hsdb.Write(func(tx *gorm.DB) error {
|
||||
if i == nil {
|
||||
return errors.New("backfilling IPs: ip allocator was nil")
|
||||
}
|
||||
|
@ -50,8 +50,9 @@ func ListPeers(tx *gorm.DB, nodeID types.NodeID) (types.Nodes, error) {
|
||||
Preload("AuthKey.User").
|
||||
Preload("User").
|
||||
Preload("Routes").
|
||||
Where("id <> ?",
|
||||
nodeID).Find(&nodes).Error; err != nil {
|
||||
Where("id <> ?", nodeID).
|
||||
Where("approved = ?", true).
|
||||
Find(&nodes).Error; err != nil {
|
||||
return types.Nodes{}, err
|
||||
}
|
||||
|
||||
@ -261,6 +262,19 @@ func RenameNode(tx *gorm.DB,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) NodeSetApprove(nodeID types.NodeID, approved bool) error {
|
||||
return hsdb.Write(func(tx *gorm.DB) error {
|
||||
return NodeSetApprove(tx, nodeID, approved)
|
||||
})
|
||||
}
|
||||
|
||||
// NodeSetApprove takes a Node struct and a set approval option
|
||||
func NodeSetApprove(tx *gorm.DB,
|
||||
nodeID types.NodeID, approved bool,
|
||||
) error {
|
||||
return tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("approved", approved).Error
|
||||
}
|
||||
|
||||
func (hsdb *HSDatabase) NodeSetExpiry(nodeID types.NodeID, expiry time.Time) error {
|
||||
return hsdb.Write(func(tx *gorm.DB) error {
|
||||
return NodeSetExpiry(tx, nodeID, expiry)
|
||||
@ -328,6 +342,8 @@ func (hsdb *HSDatabase) RegisterNodeFromAuthCallback(
|
||||
ipv6 *netip.Addr,
|
||||
) (*types.Node, error) {
|
||||
return Write(hsdb.DB, func(tx *gorm.DB) (*types.Node, error) {
|
||||
manualApprovedNode := hsdb.nodeManagement.ManualApproveNewNode
|
||||
|
||||
if node, ok := hsdb.regCache.Get(mkey.String()); ok {
|
||||
user, err := GetUserByID(tx, userID)
|
||||
if err != nil {
|
||||
@ -341,6 +357,7 @@ func (hsdb *HSDatabase) RegisterNodeFromAuthCallback(
|
||||
Str("machine_key", mkey.ShortString()).
|
||||
Str("username", user.Username()).
|
||||
Str("registrationMethod", registrationMethod).
|
||||
Bool("manualApprovedNode", manualApprovedNode).
|
||||
Str("expiresAt", fmt.Sprintf("%v", nodeExpiry)).
|
||||
Msg("Registering node from API/CLI or auth callback")
|
||||
|
||||
@ -354,6 +371,10 @@ func (hsdb *HSDatabase) RegisterNodeFromAuthCallback(
|
||||
node.User = *user
|
||||
node.RegisterMethod = registrationMethod
|
||||
|
||||
if node.IsApproved() == false && manualApprovedNode == false {
|
||||
node.Approved = true
|
||||
}
|
||||
|
||||
if nodeExpiry != nil {
|
||||
node.Expiry = nodeExpiry
|
||||
}
|
||||
@ -388,6 +409,7 @@ func RegisterNode(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Ad
|
||||
Str("machine_key", node.MachineKey.ShortString()).
|
||||
Str("node_key", node.NodeKey.ShortString()).
|
||||
Str("user", node.User.Username()).
|
||||
Bool("approved", node.IsApproved()).
|
||||
Msg("Registering node")
|
||||
|
||||
// If the node exists and it already has IP(s), we just save it
|
||||
@ -404,6 +426,7 @@ func RegisterNode(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Ad
|
||||
Str("machine_key", node.MachineKey.ShortString()).
|
||||
Str("node_key", node.NodeKey.ShortString()).
|
||||
Str("user", node.User.Username()).
|
||||
Bool("approved", node.IsApproved()).
|
||||
Msg("Node authorized again")
|
||||
|
||||
return &node, nil
|
||||
@ -428,6 +451,7 @@ func RegisterNode(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Ad
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("node", node.Hostname).
|
||||
Bool("approved", node.IsApproved()).
|
||||
Msg("Node registered with the database")
|
||||
|
||||
return &node, nil
|
||||
|
@ -25,12 +25,13 @@ var (
|
||||
func (hsdb *HSDatabase) CreatePreAuthKey(
|
||||
uid types.UserID,
|
||||
reusable bool,
|
||||
preApproved bool,
|
||||
ephemeral bool,
|
||||
expiration *time.Time,
|
||||
aclTags []string,
|
||||
) (*types.PreAuthKey, error) {
|
||||
return Write(hsdb.DB, func(tx *gorm.DB) (*types.PreAuthKey, error) {
|
||||
return CreatePreAuthKey(tx, uid, reusable, ephemeral, expiration, aclTags)
|
||||
return CreatePreAuthKey(tx, uid, reusable, preApproved, ephemeral, expiration, aclTags)
|
||||
})
|
||||
}
|
||||
|
||||
@ -39,6 +40,7 @@ func CreatePreAuthKey(
|
||||
tx *gorm.DB,
|
||||
uid types.UserID,
|
||||
reusable bool,
|
||||
preApproved bool,
|
||||
ephemeral bool,
|
||||
expiration *time.Time,
|
||||
aclTags []string,
|
||||
@ -70,14 +72,15 @@ func CreatePreAuthKey(
|
||||
}
|
||||
|
||||
key := types.PreAuthKey{
|
||||
Key: kstr,
|
||||
UserID: user.ID,
|
||||
User: *user,
|
||||
Reusable: reusable,
|
||||
Ephemeral: ephemeral,
|
||||
CreatedAt: &now,
|
||||
Expiration: expiration,
|
||||
Tags: aclTags,
|
||||
Key: kstr,
|
||||
UserID: user.ID,
|
||||
User: *user,
|
||||
Reusable: reusable,
|
||||
PreApproved: preApproved,
|
||||
Ephemeral: ephemeral,
|
||||
CreatedAt: &now,
|
||||
Expiration: expiration,
|
||||
Tags: aclTags,
|
||||
}
|
||||
|
||||
if err := tx.Save(&key).Error; err != nil {
|
||||
|
@ -158,6 +158,7 @@ func (api headscaleV1APIServer) CreatePreAuthKey(
|
||||
preAuthKey, err := api.h.db.CreatePreAuthKey(
|
||||
types.UserID(user.ID),
|
||||
request.GetReusable(),
|
||||
request.GetPreApproved(),
|
||||
request.GetEphemeral(),
|
||||
&expiration,
|
||||
request.AclTags,
|
||||
@ -362,6 +363,54 @@ func (api headscaleV1APIServer) DeleteNode(
|
||||
return &v1.DeleteNodeResponse{}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) ApproveNode(
|
||||
ctx context.Context,
|
||||
request *v1.ApproveNodeRequest,
|
||||
) (*v1.ApproveNodeResponse, error) {
|
||||
node, err := db.Write(api.h.db.DB, func(tx *gorm.DB) (*types.Node, error) {
|
||||
if err := db.NodeSetApprove(
|
||||
tx,
|
||||
types.NodeID(request.GetNodeId()),
|
||||
true,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db.GetNodeByID(tx, types.NodeID(request.GetNodeId()))
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx = types.NotifyCtx(ctx, "cli-approved-node-self", node.Hostname)
|
||||
api.h.nodeNotifier.NotifyByNodeID(
|
||||
ctx,
|
||||
types.StateUpdate{
|
||||
Type: types.StateSelfUpdate,
|
||||
ChangeNodes: []types.NodeID{node.ID},
|
||||
},
|
||||
node.ID)
|
||||
|
||||
ctx = types.NotifyCtx(ctx, "cli-approved-node-peers", node.Hostname)
|
||||
api.h.nodeNotifier.NotifyWithIgnore(
|
||||
ctx,
|
||||
types.StateUpdate{
|
||||
Type: types.StatePeerChangedPatch,
|
||||
ChangePatches: []*tailcfg.PeerChange{
|
||||
{
|
||||
NodeID: node.ID.NodeID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
node.ID)
|
||||
|
||||
log.Trace().
|
||||
Str("node", node.Hostname).
|
||||
Msg("node approved")
|
||||
|
||||
return &v1.ApproveNodeResponse{Node: node.Proto()}, nil
|
||||
}
|
||||
|
||||
func (api headscaleV1APIServer) ExpireNode(
|
||||
ctx context.Context,
|
||||
request *v1.ExpireNodeRequest,
|
||||
|
@ -110,7 +110,7 @@ func tailNode(
|
||||
|
||||
PrimaryRoutes: primaryPrefixes,
|
||||
|
||||
MachineAuthorized: !node.IsExpired(),
|
||||
MachineAuthorized: node.IsApproved(),
|
||||
Expired: node.IsExpired(),
|
||||
}
|
||||
|
||||
|
@ -87,6 +87,8 @@ type Config struct {
|
||||
Policy PolicyConfig
|
||||
|
||||
Tuning Tuning
|
||||
|
||||
NodeManagement NodeManagement
|
||||
}
|
||||
|
||||
type DNSConfig struct {
|
||||
@ -214,6 +216,10 @@ type Tuning struct {
|
||||
NodeMapSessionBufferedChanSize int
|
||||
}
|
||||
|
||||
type NodeManagement struct {
|
||||
ManualApproveNewNode bool
|
||||
}
|
||||
|
||||
// LoadConfig prepares and loads the Headscale configuration into Viper.
|
||||
// This means it sets the default values, reads the configuration file and
|
||||
// environment variables, and handles deprecated configuration options.
|
||||
@ -292,6 +298,8 @@ func LoadConfig(path string, isFile bool) error {
|
||||
viper.SetDefault("tuning.batch_change_delay", "800ms")
|
||||
viper.SetDefault("tuning.node_mapsession_buffered_chan_size", 30)
|
||||
|
||||
viper.SetDefault("node_management.manual_approve_new_node", false)
|
||||
|
||||
viper.SetDefault("prefixes.allocation", string(IPAllocationStrategySequential))
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
@ -749,6 +757,14 @@ func prefixV6() (*netip.Prefix, error) {
|
||||
return &prefixV6, nil
|
||||
}
|
||||
|
||||
func nodeManagementConfig() NodeManagement {
|
||||
manualApproveNewNode := viper.GetBool("node_management.manual_approve_new_node")
|
||||
|
||||
return NodeManagement{
|
||||
ManualApproveNewNode: manualApproveNewNode,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadCLIConfig returns the needed configuration for the CLI client
|
||||
// of Headscale to connect to a Headscale server.
|
||||
func LoadCLIConfig() (*Config, error) {
|
||||
@ -845,6 +861,8 @@ func LoadServerConfig() (*Config, error) {
|
||||
}
|
||||
}
|
||||
|
||||
nodeManagement := nodeManagementConfig()
|
||||
|
||||
return &Config{
|
||||
ServerURL: serverURL,
|
||||
Addr: viper.GetString("listen_addr"),
|
||||
@ -936,6 +954,8 @@ func LoadServerConfig() (*Config, error) {
|
||||
"tuning.node_mapsession_buffered_chan_size",
|
||||
),
|
||||
},
|
||||
|
||||
NodeManagement: nodeManagement,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -83,6 +83,7 @@ type Node struct {
|
||||
|
||||
LastSeen *time.Time
|
||||
Expiry *time.Time
|
||||
Approved bool `sql:"DEFAULT:false"`
|
||||
|
||||
Routes []Route `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
|
||||
@ -114,6 +115,11 @@ func (node Node) IsExpired() bool {
|
||||
return time.Since(*node.Expiry) > 0
|
||||
}
|
||||
|
||||
// IsApproved returns whether the node is approved.
|
||||
func (node Node) IsApproved() bool {
|
||||
return node.Approved == true
|
||||
}
|
||||
|
||||
// IsEphemeral returns if the node is registered as an Ephemeral node.
|
||||
// https://tailscale.com/kb/1111/ephemeral-nodes/
|
||||
func (node *Node) IsEphemeral() bool {
|
||||
@ -249,6 +255,7 @@ func (node *Node) Proto() *v1.Node {
|
||||
ForcedTags: node.ForcedTags,
|
||||
|
||||
RegisterMethod: node.RegisterMethodToV1Enum(),
|
||||
Approved: node.Approved,
|
||||
|
||||
CreatedAt: timestamppb.New(node.CreatedAt),
|
||||
}
|
||||
|
@ -11,14 +11,15 @@ import (
|
||||
|
||||
// PreAuthKey describes a pre-authorization key usable in a particular user.
|
||||
type PreAuthKey struct {
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
Key string
|
||||
UserID uint
|
||||
User User `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
Reusable bool
|
||||
Ephemeral bool `gorm:"default:false"`
|
||||
Used bool `gorm:"default:false"`
|
||||
Tags []string `gorm:"serializer:json"`
|
||||
ID uint64 `gorm:"primary_key"`
|
||||
Key string
|
||||
UserID uint
|
||||
User User `gorm:"constraint:OnDelete:CASCADE;"`
|
||||
Reusable bool
|
||||
PreApproved bool `gorm:"default:false"`
|
||||
Ephemeral bool `gorm:"default:false"`
|
||||
Used bool `gorm:"default:false"`
|
||||
Tags []string `gorm:"serializer:json"`
|
||||
|
||||
CreatedAt *time.Time
|
||||
Expiration *time.Time
|
||||
@ -26,13 +27,14 @@ type PreAuthKey struct {
|
||||
|
||||
func (key *PreAuthKey) Proto() *v1.PreAuthKey {
|
||||
protoKey := v1.PreAuthKey{
|
||||
User: key.User.Username(),
|
||||
Id: strconv.FormatUint(key.ID, util.Base10),
|
||||
Key: key.Key,
|
||||
Ephemeral: key.Ephemeral,
|
||||
Reusable: key.Reusable,
|
||||
Used: key.Used,
|
||||
AclTags: key.Tags,
|
||||
User: key.User.Username(),
|
||||
Id: strconv.FormatUint(key.ID, util.Base10),
|
||||
Key: key.Key,
|
||||
Ephemeral: key.Ephemeral,
|
||||
PreApproved: key.PreApproved,
|
||||
Reusable: key.Reusable,
|
||||
Used: key.Used,
|
||||
AclTags: key.Tags,
|
||||
}
|
||||
|
||||
if key.Expiration != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user