diff --git a/cmd/admin_user_generate_authorized_integration.go b/cmd/admin_user_generate_authorized_integration.go index fc159d90ae..ca39ac1b6f 100644 --- a/cmd/admin_user_generate_authorized_integration.go +++ b/cmd/admin_user_generate_authorized_integration.go @@ -38,6 +38,15 @@ enable-openid-connect flag in a workflow.`, Usage: "Username", Required: true, }, + &cli.StringFlag{ + Name: "name", + Usage: "Name of the authorized integration for later identification", + Required: true, + }, + &cli.StringFlag{ + Name: "description", + Usage: "Optional description for the authorized integration", + }, // JWT validation: &cli.StringFlag{ @@ -92,7 +101,9 @@ func runCreateAuthorizedIntegration(ctx context.Context, c *cli.Command) error { } ai := &auth_model.AuthorizedIntegration{ - UserID: user.ID, + UserID: user.ID, + Name: c.String("name"), + Description: c.String("description"), } var rules []auth_model.ClaimRule @@ -171,14 +182,18 @@ func runCreateAuthorizedIntegration(ctx context.Context, c *cli.Command) error { Value string `json:"value"` } output := struct { - Message string `json:"message"` - Issuer string `json:"issuer"` - Audience string `json:"audience"` - ClaimRules []ClaimRuleDescription `json:"claim_rules"` + Message string `json:"message"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Issuer string `json:"issuer"` + Audience string `json:"audience"` + ClaimRules []ClaimRuleDescription `json:"claim_rules"` }{ - Message: "Authorized integration was successfully created.", - Issuer: ai.Issuer, - Audience: ai.Audience, + Message: "Authorized integration was successfully created.", + Name: ai.Name, + Description: ai.Description, + Issuer: ai.Issuer, + Audience: ai.Audience, } for _, cr := range ai.ClaimRules.Rules { var description string diff --git a/models/auth/authorized_integration.go b/models/auth/authorized_integration.go index e4cc8cedb0..ca5e103146 100644 --- a/models/auth/authorized_integration.go +++ b/models/auth/authorized_integration.go @@ -30,6 +30,9 @@ type AuthorizedIntegration struct { Scope AccessTokenScope `xorm:"NOT NULL"` ResourceAllRepos bool `xorm:"NOT NULL"` // flag for whether AuthorizedIntegrationResourceRepo instances will limit the resources this access token can access (false) or won't limit them (true). + Name string // short name for lists of authorized integrations + Description string `xorm:"LONGTEXT"` // long description, optional to document relevant details of the integration + // Exact-match `iss` claim of the JWT Issuer string `xorm:"NOT NULL UNIQUE(s)"` // Exact-match `aud` claim of the JWT diff --git a/models/forgejo_migrations/v16b_authorized_integration_name_description.go b/models/forgejo_migrations/v16b_authorized_integration_name_description.go new file mode 100644 index 0000000000..3f5450310f --- /dev/null +++ b/models/forgejo_migrations/v16b_authorized_integration_name_description.go @@ -0,0 +1,60 @@ +// Copyright 2026 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations + +import ( + "context" + "fmt" + + "forgejo.org/models/db" + "forgejo.org/modules/timeutil" + + "xorm.io/xorm" +) + +func init() { + registerMigration(&Migration{ + Description: "add name & description to authorized_integration", + Upgrade: addAuthorizedIntegrationNameDescription, + }) +} + +func addAuthorizedIntegrationNameDescription(x *xorm.Engine) error { + type AuthorizedIntegration struct { + // New fields: + Name string + Description string `xorm:"LONGTEXT"` + + // Existing fields, used for UPDATE in migration: + ID int64 `xorm:"pk autoincr"` + Issuer string `xorm:"NOT NULL UNIQUE(s)"` + Audience string `xorm:"NOT NULL UNIQUE(s)"` + CreatedUnix timeutil.TimeStamp `xorm:"NOT NULL created"` + // don't include `UpdatedUnix`, so the updated timestamp isn't bumped when Name is set in migration + } + + _, err := x.SyncWithOptions( + xorm.SyncOptions{IgnoreDropIndices: true}, + new(AuthorizedIntegration), + ) + if err != nil { + return err + } + + // As v16a has creating this table, v16b will likely have no records for any users. But for developers working on + // v16, populate "Name" with a quick computed value: + return db.Iterate(db.DefaultContext, nil, func(ctx context.Context, ai *AuthorizedIntegration) error { + ai.Name = fmt.Sprintf("%s created %s", ai.Issuer, ai.CreatedUnix.FormatDate()) + r, err := db.GetEngine(ctx). + ID(ai.ID). + Cols("name"). + Update(ai) + if err != nil { + return err + } else if r != 1 { + return fmt.Errorf("UPDATE expected to affect 1 row, but was %d", r) + } + return nil + }) +}