Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow sending reports to external users with magic tokens #5778

Merged
merged 23 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions admin/auth_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ type IssueMagicAuthTokenOptions struct {
Fields []string
State string
Title string
Internal bool
begelundmuller marked this conversation as resolved.
Show resolved Hide resolved
}

// IssueMagicAuthToken generates and persists a new magic auth token for a project.
Expand All @@ -203,6 +204,7 @@ func (s *Service) IssueMagicAuthToken(ctx context.Context, opts *IssueMagicAuthT
Fields: opts.Fields,
State: opts.State,
Title: opts.Title,
Internal: opts.Internal,
})
if err != nil {
return nil, err
Expand Down
28 changes: 28 additions & 0 deletions admin/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,13 @@ type DB interface {
InsertMagicAuthToken(ctx context.Context, opts *InsertMagicAuthTokenOptions) (*MagicAuthToken, error)
UpdateMagicAuthTokenUsedOn(ctx context.Context, ids []string) error
DeleteMagicAuthToken(ctx context.Context, id string) error
DeleteMagicAuthTokens(ctx context.Context, ids []string) error
DeleteExpiredMagicAuthTokens(ctx context.Context, retention time.Duration) error

FindReportTokens(ctx context.Context, reportName string) ([]*ReportToken, error)
FindReportTokensWithSecret(ctx context.Context, reportName string) ([]*ReportTokenWithSecret, error)
InsertReportToken(ctx context.Context, opts *InsertReportTokenOptions) (*ReportToken, error)

FindDeviceAuthCodeByDeviceCode(ctx context.Context, deviceCode string) (*DeviceAuthCode, error)
FindPendingDeviceAuthCodeByUserCode(ctx context.Context, userCode string) (*DeviceAuthCode, error)
InsertDeviceAuthCode(ctx context.Context, deviceCode, userCode, clientID string, expiresOn time.Time) (*DeviceAuthCode, error)
Expand Down Expand Up @@ -637,6 +642,7 @@ type MagicAuthToken struct {
Fields []string `db:"fields"`
State string `db:"state"`
Title string `db:"title"`
Internal bool `db:"internal"`
}

// MagicAuthTokenWithUser is a MagicAuthToken with additional information about the user who created it.
Expand All @@ -660,6 +666,28 @@ type InsertMagicAuthTokenOptions struct {
Fields []string
State string
Title string
Internal bool
}

type ReportToken struct {
ID string
ReportName string `db:"report_name"`
RecipientEmail string `db:"recipient_email"`
MagicAuthTokenID string `db:"magic_auth_token_id"`
}

type ReportTokenWithSecret struct {
ID string
ReportName string `db:"report_name"`
RecipientEmail string `db:"recipient_email"`
MagicAuthTokenID string `db:"magic_auth_token_id"`
MagicAuthTokenSecret []byte `db:"magic_auth_token_secret"`
}

type InsertReportTokenOptions struct {
ReportName string
RecipientEmail string
MagicAuthTokenID string
}

// AuthClient is a client that requests and consumes auth tokens.
Expand Down
10 changes: 10 additions & 0 deletions admin/database/postgres/migrations/0049.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ALTER TABLE magic_auth_tokens ADD COLUMN internal BOOLEAN NOT NULL DEFAULT FALSE;

CREATE TABLE report_tokens (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
report_name TEXT NOT NULL,
pjain1 marked this conversation as resolved.
Show resolved Hide resolved
recipient_email TEXT NOT NULL,
magic_auth_token_id UUID NOT NULL REFERENCES magic_auth_tokens (id) ON DELETE CASCADE
);

CREATE INDEX report_tokens_report_name_idx ON report_tokens (report_name);
pjain1 marked this conversation as resolved.
Show resolved Hide resolved
71 changes: 66 additions & 5 deletions admin/database/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -1112,7 +1112,7 @@ func (c *connection) FindMagicAuthTokensWithUser(ctx context.Context, projectID
n++
}

where += " AND (t.expires_on IS NULL OR t.expires_on > now())"
where += " AND (t.expires_on IS NULL OR t.expires_on > now()) AND t.internal=false"

qry := fmt.Sprintf("SELECT t.*, u.email AS created_by_user_email FROM magic_auth_tokens t LEFT JOIN users u ON t.created_by_user_id=u.id WHERE %s ORDER BY t.id LIMIT $%d", where, n)
args = append(args, limit)
Expand Down Expand Up @@ -1145,7 +1145,7 @@ func (c *connection) FindMagicAuthToken(ctx context.Context, id string, withSecr

func (c *connection) FindMagicAuthTokenWithUser(ctx context.Context, id string) (*database.MagicAuthTokenWithUser, error) {
res := &magicAuthTokenWithUserDTO{}
err := c.getDB(ctx).QueryRowxContext(ctx, "SELECT t.*, u.email AS created_by_user_email FROM magic_auth_tokens t LEFT JOIN users u ON t.created_by_user_id=u.id WHERE t.id=$1", id).StructScan(res)
err := c.getDB(ctx).QueryRowxContext(ctx, "SELECT t.*, u.email AS created_by_user_email FROM magic_auth_tokens t LEFT JOIN users u ON t.created_by_user_id=u.id WHERE t.id=$1 AND t.internal=false", id).StructScan(res)
if err != nil {
return nil, parseErr("magic auth token", err)
}
Expand All @@ -1168,9 +1168,9 @@ func (c *connection) InsertMagicAuthToken(ctx context.Context, opts *database.In

res := &magicAuthTokenDTO{}
err = c.getDB(ctx).QueryRowxContext(ctx, `
INSERT INTO magic_auth_tokens (id, secret_hash, secret, secret_encryption_key_id, project_id, expires_on, created_by_user_id, attributes, resource_type, resource_name, filter_json, fields, state, title)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *`,
opts.ID, opts.SecretHash, encSecret, encKeyID, opts.ProjectID, opts.ExpiresOn, opts.CreatedByUserID, opts.Attributes, opts.ResourceType, opts.ResourceName, opts.FilterJSON, opts.Fields, opts.State, opts.Title,
INSERT INTO magic_auth_tokens (id, secret_hash, secret, secret_encryption_key_id, project_id, expires_on, created_by_user_id, attributes, resource_type, resource_name, filter_json, fields, state, title, internal)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING *`,
opts.ID, opts.SecretHash, encSecret, encKeyID, opts.ProjectID, opts.ExpiresOn, opts.CreatedByUserID, opts.Attributes, opts.ResourceType, opts.ResourceName, opts.FilterJSON, opts.Fields, opts.State, opts.Title, opts.Internal,
).StructScan(res)
if err != nil {
return nil, parseErr("magic auth token", err)
Expand All @@ -1191,11 +1191,55 @@ func (c *connection) DeleteMagicAuthToken(ctx context.Context, id string) error
return checkDeleteRow("magic auth token", res, err)
}

func (c *connection) DeleteMagicAuthTokens(ctx context.Context, ids []string) error {
res, err := c.getDB(ctx).ExecContext(ctx, "DELETE FROM magic_auth_tokens WHERE id=ANY($1)", ids)
return checkDeleteRow("magic auth token", res, err)
}

func (c *connection) DeleteExpiredMagicAuthTokens(ctx context.Context, retention time.Duration) error {
_, err := c.getDB(ctx).ExecContext(ctx, "DELETE FROM magic_auth_tokens WHERE expires_on IS NOT NULL AND expires_on + $1 < now()", retention)
return parseErr("magic auth token", err)
}

func (c *connection) FindReportTokens(ctx context.Context, reportName string) ([]*database.ReportToken, error) {
var res []*database.ReportToken
err := c.getDB(ctx).SelectContext(ctx, &res, `SELECT * FROM report_tokens WHERE report_name=$1`, reportName)
if err != nil {
return nil, parseErr("report tokens", err)
}
return res, nil
}

func (c *connection) FindReportTokensWithSecret(ctx context.Context, reportName string) ([]*database.ReportTokenWithSecret, error) {
var res []*reportTokenWithSecretDTO
err := c.getDB(ctx).SelectContext(ctx, &res, `SELECT t.*, m.secret as magic_auth_token_secret, m.secret_encryption_key_id FROM report_tokens t JOIN magic_auth_tokens m ON t.magic_auth_token_id=m.id WHERE t.report_name=$1`, reportName)
if err != nil {
return nil, parseErr("report tokens", err)
}

ret := make([]*database.ReportTokenWithSecret, len(res))
for i, dto := range res {
ret[i], err = c.reportTokenWithSecretFromDTO(dto)
if err != nil {
return nil, err
}
}
return ret, nil
}

func (c *connection) InsertReportToken(ctx context.Context, opts *database.InsertReportTokenOptions) (*database.ReportToken, error) {
if err := database.Validate(opts); err != nil {
return nil, err
}

res := &database.ReportToken{}
err := c.getDB(ctx).QueryRowxContext(ctx, `INSERT INTO report_tokens (report_name, recipient_email, magic_auth_token_id) VALUES ($1, $2, $3) RETURNING *`, opts.ReportName, opts.RecipientEmail, opts.MagicAuthTokenID).StructScan(res)
if err != nil {
return nil, parseErr("report token", err)
}
return res, nil
}

func (c *connection) FindDeviceAuthCodeByDeviceCode(ctx context.Context, deviceCode string) (*database.DeviceAuthCode, error) {
authCode := &database.DeviceAuthCode{}
err := c.getDB(ctx).QueryRowxContext(ctx, "SELECT * FROM device_auth_codes WHERE device_code = $1", deviceCode).StructScan(authCode)
Expand Down Expand Up @@ -2238,6 +2282,23 @@ func (c *connection) magicAuthTokenWithUserFromDTO(dto *magicAuthTokenWithUserDT
return dto.MagicAuthTokenWithUser, nil
}

type reportTokenWithSecretDTO struct {
*database.ReportTokenWithSecret
SecretEncryptionKeyID string `db:"secret_encryption_key_id"`
}

func (c *connection) reportTokenWithSecretFromDTO(dto *reportTokenWithSecretDTO) (*database.ReportTokenWithSecret, error) {
if dto.SecretEncryptionKeyID == "" {
return dto.ReportTokenWithSecret, nil
}
decrypted, err := c.decrypt(dto.ReportTokenWithSecret.MagicAuthTokenSecret, dto.SecretEncryptionKeyID)
if err != nil {
return nil, err
}
dto.ReportTokenWithSecret.MagicAuthTokenSecret = decrypted
return dto.ReportTokenWithSecret, nil
}

type organizationInviteDTO struct {
*database.OrganizationInvite
UsergroupIDs pgtype.TextArray `db:"usergroup_ids"`
Expand Down
2 changes: 1 addition & 1 deletion admin/server/billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (s *Server) UpdateBillingSubscription(ctx context.Context, req *adminv1.Upd
}, nil
}

// CancelBillingSubscription cancels the billing subscription for the organization and puts them on default plan
// CancelBillingSubscription cancels the billing subscription for the organization
func (s *Server) CancelBillingSubscription(ctx context.Context, req *adminv1.CancelBillingSubscriptionRequest) (*adminv1.CancelBillingSubscriptionResponse, error) {
observability.AddRequestAttributes(ctx, attribute.String("args.org", req.Organization))

Expand Down
Loading
Loading