Rename user into identity

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-12-20 14:14:31 +01:00
parent fa0295f481
commit 0f7c755d53
35 changed files with 606 additions and 664 deletions

View File

@@ -26,9 +26,9 @@ import (
) )
type ( type (
UserAPIKeyMembership struct { PersonalAPIKeyMembership struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
UserAPIKeyID gid.GID `db:"auth_user_api_key_id"` PersonalAPIKeyID gid.GID `db:"auth_personal_api_key_id"`
MembershipID gid.GID `db:"membership_id"` MembershipID gid.GID `db:"membership_id"`
Role APIRole `db:"role"` Role APIRole `db:"role"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
@@ -37,21 +37,21 @@ type (
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
} }
UserAPIKeyMemberships []*UserAPIKeyMembership PersonalAPIKeyMemberships []*PersonalAPIKeyMembership
) )
func (a *UserAPIKeyMembership) Insert( func (a *PersonalAPIKeyMembership) Insert(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
) error { ) error {
q := ` q := `
INSERT INTO INSERT INTO
authz_api_keys_memberships (id, tenant_id, auth_user_api_key_id, membership_id, role, organization_id, created_at, updated_at) authz_api_keys_memberships (id, tenant_id, auth_personal_api_key_id, membership_id, role, organization_id, created_at, updated_at)
VALUES ( VALUES (
@id, @id,
@tenant_id, @tenant_id,
@auth_user_api_key_id, @auth_personal_api_key_id,
@membership_id, @membership_id,
@role, @role,
@organization_id, @organization_id,
@@ -61,34 +61,34 @@ VALUES (
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"id": a.ID, "id": a.ID,
"tenant_id": scope.GetTenantID(), "tenant_id": scope.GetTenantID(),
"auth_user_api_key_id": a.UserAPIKeyID, "auth_personal_api_key_id": a.PersonalAPIKeyID,
"membership_id": a.MembershipID, "membership_id": a.MembershipID,
"role": a.Role, "role": a.Role,
"organization_id": a.OrganizationID, "organization_id": a.OrganizationID,
"created_at": a.CreatedAt, "created_at": a.CreatedAt,
"updated_at": a.UpdatedAt, "updated_at": a.UpdatedAt,
} }
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot insert user api key membership: %w", err) return fmt.Errorf("cannot insert personal api key membership: %w", err)
} }
return nil return nil
} }
func (a *UserAPIKeyMemberships) LoadByUserAPIKeyID( func (a *PersonalAPIKeyMemberships) LoadByPersonalAPIKeyID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
userAPIKeyID gid.GID, personalAPIKeyID gid.GID,
) error { ) error {
q := ` q := `
SELECT SELECT
akm.id, akm.id,
akm.auth_user_api_key_id, akm.auth_personal_api_key_id,
akm.membership_id, akm.membership_id,
akm.role, akm.role,
akm.created_at, akm.created_at,
@@ -102,7 +102,7 @@ JOIN
JOIN JOIN
organizations o ON m.organization_id = o.id organizations o ON m.organization_id = o.id
WHERE WHERE
akm.auth_user_api_key_id = @auth_user_api_key_id akm.auth_personal_api_key_id = @auth_personal_api_key_id
AND m.%s AND m.%s
ORDER BY akm.created_at DESC ORDER BY akm.created_at DESC
` `
@@ -110,18 +110,18 @@ ORDER BY akm.created_at DESC
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"auth_user_api_key_id": userAPIKeyID, "auth_personal_api_key_id": personalAPIKeyID,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user api key memberships: %w", err) return fmt.Errorf("cannot query personal api key memberships: %w", err)
} }
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[UserAPIKeyMembership]) memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PersonalAPIKeyMembership])
if err != nil { if err != nil {
return fmt.Errorf("cannot collect user api key memberships: %w", err) return fmt.Errorf("cannot collect personal api key memberships: %w", err)
} }
*a = memberships *a = memberships
@@ -130,7 +130,7 @@ ORDER BY akm.created_at DESC
} }
// LoadRoleByAPIKeyAndEntityID loads an API key's role by querying any entity to extract its organization_id // LoadRoleByAPIKeyAndEntityID loads an API key's role by querying any entity to extract its organization_id
func (a *UserAPIKeyMembership) LoadRoleByAPIKeyAndEntityID( func (a *PersonalAPIKeyMembership) LoadRoleByAPIKeyAndEntityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
@@ -152,7 +152,7 @@ func (a *UserAPIKeyMembership) LoadRoleByAPIKeyAndEntityID(
query := fmt.Sprintf(` query := fmt.Sprintf(`
SELECT SELECT
akm.id, akm.id,
akm.auth_user_api_key_id, akm.auth_personal_api_key_id,
akm.membership_id, akm.membership_id,
akm.role, akm.role,
akm.created_at, akm.created_at,
@@ -163,7 +163,7 @@ FROM
INNER JOIN %s e ON e.id = @entity_id INNER JOIN %s e ON e.id = @entity_id
WHERE WHERE
%s %s
AND akm.auth_user_api_key_id = @api_key_id AND akm.auth_personal_api_key_id = @api_key_id
AND m.organization_id = e.organization_id AND m.organization_id = e.organization_id
LIMIT 1; LIMIT 1;
`, tableName, scope.SQLFragment()) `, tableName, scope.SQLFragment())
@@ -184,10 +184,10 @@ LIMIT 1;
return fmt.Errorf("API key membership not found for key %s and entity %s", apiKeyID, entityID) return fmt.Errorf("API key membership not found for key %s and entity %s", apiKeyID, entityID)
} }
var membership UserAPIKeyMembership var membership PersonalAPIKeyMembership
err = rows.Scan( err = rows.Scan(
&membership.ID, &membership.ID,
&membership.UserAPIKeyID, &membership.PersonalAPIKeyID,
&membership.MembershipID, &membership.MembershipID,
&membership.Role, &membership.Role,
&membership.CreatedAt, &membership.CreatedAt,
@@ -202,7 +202,7 @@ LIMIT 1;
return nil return nil
} }
func (a *UserAPIKeyMembership) LoadByAPIKeyIDAndOrganizationID( func (a *PersonalAPIKeyMembership) LoadByAPIKeyIDAndOrganizationID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
@@ -212,7 +212,7 @@ func (a *UserAPIKeyMembership) LoadByAPIKeyIDAndOrganizationID(
q := ` q := `
SELECT SELECT
akm.id, akm.id,
akm.auth_user_api_key_id, akm.auth_personal_api_key_id,
akm.membership_id, akm.membership_id,
akm.role, akm.role,
akm.created_at, akm.created_at,
@@ -226,7 +226,7 @@ JOIN
JOIN JOIN
organizations o ON m.organization_id = o.id organizations o ON m.organization_id = o.id
WHERE WHERE
akm.auth_user_api_key_id = @api_key_id akm.auth_personal_api_key_id = @api_key_id
AND m.organization_id = @organization_id AND m.organization_id = @organization_id
AND m.%s AND m.%s
` `
@@ -241,22 +241,22 @@ WHERE
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user api key membership: %w", err) return fmt.Errorf("cannot query personal api key membership: %w", err)
} }
membership, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[UserAPIKeyMembership]) membership, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PersonalAPIKeyMembership])
if err != nil { if err != nil {
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
return fmt.Errorf("API key does not have access to organization") return fmt.Errorf("API key does not have access to organization")
} }
return fmt.Errorf("cannot collect user api key membership: %w", err) return fmt.Errorf("cannot collect personal api key membership: %w", err)
} }
*a = membership *a = membership
return nil return nil
} }
func (a *UserAPIKeyMembership) Delete( func (a *PersonalAPIKeyMembership) Delete(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
@@ -278,13 +278,13 @@ WHERE
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot delete user api key membership: %w", err) return fmt.Errorf("cannot delete personal api key membership: %w", err)
} }
return nil return nil
} }
func (a *UserAPIKeyMemberships) LoadByMembershipID( func (a *PersonalAPIKeyMemberships) LoadByMembershipID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
@@ -293,7 +293,7 @@ func (a *UserAPIKeyMemberships) LoadByMembershipID(
q := ` q := `
SELECT SELECT
akm.id, akm.id,
akm.auth_user_api_key_id, akm.auth_personal_api_key_id,
akm.membership_id, akm.membership_id,
akm.role, akm.role,
akm.created_at, akm.created_at,
@@ -321,12 +321,12 @@ ORDER BY akm.created_at DESC
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user api key memberships by membership id: %w", err) return fmt.Errorf("cannot query personal api key memberships by membership id: %w", err)
} }
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[UserAPIKeyMembership]) memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PersonalAPIKeyMembership])
if err != nil { if err != nil {
return fmt.Errorf("cannot collect user api key memberships: %w", err) return fmt.Errorf("cannot collect personal api key memberships: %w", err)
} }
*a = memberships *a = memberships
@@ -334,25 +334,25 @@ ORDER BY akm.created_at DESC
return nil return nil
} }
func DeleteAllUserAPIKeyMembershipsByUserAPIKeyID( func DeleteAllPersonalAPIKeyMembershipsByPersonalAPIKeyID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
userAPIKeyID gid.GID, personalAPIKeyID gid.GID,
) error { ) error {
q := ` q := `
DELETE FROM DELETE FROM
authz_api_keys_memberships authz_api_keys_memberships
WHERE WHERE
auth_user_api_key_id = @auth_user_api_key_id auth_personal_api_key_id = @auth_personal_api_key_id
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"auth_user_api_key_id": userAPIKeyID, "auth_personal_api_key_id": personalAPIKeyID,
} }
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot delete user api key memberships: %w", err) return fmt.Errorf("cannot delete personal api key memberships: %w", err)
} }
return nil return nil

View File

@@ -32,7 +32,7 @@ const (
PeopleEntityType uint16 = 8 PeopleEntityType uint16 = 8
VendorComplianceReportEntityType uint16 = 9 VendorComplianceReportEntityType uint16 = 9
DocumentEntityType uint16 = 10 DocumentEntityType uint16 = 10
UserEntityType uint16 = 11 IdentityEntityType uint16 = 11
SessionEntityType uint16 = 12 SessionEntityType uint16 = 12
EmailEntityType uint16 = 13 EmailEntityType uint16 = 13
ControlEntityType uint16 = 14 ControlEntityType uint16 = 14
@@ -64,8 +64,8 @@ const (
SlackMessageEntityType uint16 = 40 SlackMessageEntityType uint16 = 40
TrustCenterFileEntityType uint16 = 41 TrustCenterFileEntityType uint16 = 41
SAMLConfigurationEntityType uint16 = 42 SAMLConfigurationEntityType uint16 = 42
UserAPIKeyEntityType uint16 = 43 PersonalAPIKeyEntityType uint16 = 43
UserAPIKeyMembershipEntityType uint16 = 44 PersonalAPIKeyMembershipEntityType uint16 = 44
MeetingEntityType uint16 = 45 MeetingEntityType uint16 = 45
DataProtectionImpactAssessmentEntityType uint16 = 46 DataProtectionImpactAssessmentEntityType uint16 = 46
TransferImpactAssessmentEntityType uint16 = 47 TransferImpactAssessmentEntityType uint16 = 47
@@ -124,9 +124,9 @@ var entityRegistry = map[uint16]EntityInfo{
Model: "Document", Model: "Document",
Table: "documents", Table: "documents",
}, },
UserEntityType: { IdentityEntityType: {
Model: "User", Model: "Identity",
Table: "auth_users", Table: "identities",
}, },
SessionEntityType: { SessionEntityType: {
Model: "Session", Model: "Session",
@@ -252,12 +252,12 @@ var entityRegistry = map[uint16]EntityInfo{
Model: "SAMLConfiguration", Model: "SAMLConfiguration",
Table: "auth_saml_configurations", Table: "auth_saml_configurations",
}, },
UserAPIKeyEntityType: { PersonalAPIKeyEntityType: {
Model: "UserAPIKey", Model: "PersonalAPIKey",
Table: "auth_user_api_keys", Table: "auth_personal_api_keys",
}, },
UserAPIKeyMembershipEntityType: { PersonalAPIKeyMembershipEntityType: {
Model: "UserAPIKeyMembership", Model: "PersonalAPIKeyMembership",
Table: "authz_api_keys_memberships", Table: "authz_api_keys_memberships",
}, },
MeetingEntityType: { MeetingEntityType: {

View File

@@ -31,34 +31,34 @@ import (
) )
type ( type (
User struct { Identity struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
EmailAddress mail.Addr `db:"email_address"` EmailAddress mail.Addr `db:"email_address"`
HashedPassword []byte `db:"hashed_password"` HashedPassword []byte `db:"hashed_password"`
FullName string `db:"fullname"` FullName string `db:"fullname"`
EmailAddressVerified bool `db:"email_address_verified"` EmailAddressVerified bool `db:"email_address_verified"`
SAMLSubject *string `db:"saml_subject"` SAMLSubject *string `db:"saml_subject"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
} }
Users []*User Identities []*Identity
) )
func (u User) CursorKey(orderBy UserOrderField) page.CursorKey { func (i Identity) CursorKey(orderBy IdentityOrderField) page.CursorKey {
switch orderBy { switch orderBy {
case UserOrderFieldCreatedAt: case IdentityOrderFieldCreatedAt:
return page.NewCursorKey(u.ID, u.CreatedAt) return page.NewCursorKey(i.ID, i.CreatedAt)
} }
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (u *Users) LoadByOrganizationID( func (i *Identities) LoadByOrganizationID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
organizationID gid.GID, organizationID gid.GID,
cursor *page.Cursor[UserOrderField], cursor *page.Cursor[IdentityOrderField],
) error { ) error {
q := ` q := `
SELECT SELECT
@@ -70,10 +70,10 @@ SELECT
created_at, created_at,
updated_at updated_at
FROM FROM
users identities
WHERE WHERE
id IN ( id IN (
SELECT user_id FROM authz_memberships WHERE organization_id = @organization_id SELECT identity_id FROM authz_memberships WHERE organization_id = @organization_id
) )
AND %s AND %s
` `
@@ -85,20 +85,20 @@ WHERE
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query users: %w", err) return fmt.Errorf("cannot query identities: %w", err)
} }
users, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[User]) identities, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Identity])
if err != nil { if err != nil {
return fmt.Errorf("cannot collect users: %w", err) return fmt.Errorf("cannot collect identities: %w", err)
} }
*u = users *i = identities
return nil return nil
} }
func (u *Users) CountByOrganizationID( func (i *Identities) CountByOrganizationID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
@@ -108,10 +108,10 @@ func (u *Users) CountByOrganizationID(
SELECT SELECT
COUNT(*) COUNT(*)
FROM FROM
users identities
WHERE WHERE
id IN ( id IN (
SELECT user_id FROM authz_memberships WHERE organization_id = @organization_id AND %s SELECT identity_id FROM authz_memberships WHERE organization_id = @organization_id AND %s
) )
` `
@@ -125,14 +125,14 @@ WHERE
var count int var count int
err := row.Scan(&count) err := row.Scan(&count)
if err != nil { if err != nil {
return 0, fmt.Errorf("cannot count users: %w", err) return 0, fmt.Errorf("cannot count identities: %w", err)
} }
return count, nil return count, nil
} }
// Tenant id scope is not applied because we want to access users across all tenants for authentication purposes. // Tenant id scope is not applied because we want to access identities across all tenants for authentication purposes.
func (u *User) LoadByEmail( func (i *Identity) LoadByEmail(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
email mail.Addr, email mail.Addr,
@@ -148,38 +148,38 @@ SELECT
created_at, created_at,
updated_at updated_at
FROM FROM
users identities
WHERE WHERE
email_address = @user_email email_address = @identity_email
LIMIT 1; LIMIT 1;
` `
args := pgx.StrictNamedArgs{"user_email": email} args := pgx.StrictNamedArgs{"identity_email": email}
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user: %w", err) return fmt.Errorf("cannot query identity: %w", err)
} }
user, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[User]) identity, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Identity])
if err != nil { if err != nil {
if errors.Is(err, pgx.ErrNoRows) { if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound return ErrResourceNotFound
} }
return fmt.Errorf("cannot collect user: %w", err) return fmt.Errorf("cannot collect identity: %w", err)
} }
*u = user *i = identity
return nil return nil
} }
// Tenant id scope is not applied because we want to access users across all tenants for authentication purposes. // Tenant id scope is not applied because we want to access identities across all tenants for authentication purposes.
func (u *User) LoadByID( func (i *Identity) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
userID gid.GID, identityID gid.GID,
) error { ) error {
q := ` q := `
SELECT SELECT
@@ -192,42 +192,42 @@ SELECT
created_at, created_at,
updated_at updated_at
FROM FROM
users identities
WHERE WHERE
id = @user_id id = @identity_id
LIMIT 1; LIMIT 1;
` `
args := pgx.StrictNamedArgs{"user_id": userID} args := pgx.StrictNamedArgs{"identity_id": identityID}
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user: %w", err) return fmt.Errorf("cannot query identity: %w", err)
} }
user, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[User]) identity, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Identity])
if err != nil { if err != nil {
if errors.Is(err, pgx.ErrNoRows) { if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound return ErrResourceNotFound
} }
return fmt.Errorf("cannot collect user: %w", err) return fmt.Errorf("cannot collect identity: %w", err)
} }
*u = user *i = identity
return nil return nil
} }
func (u *User) Insert( func (i *Identity) Insert(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
) error { ) error {
q := ` q := `
INSERT INTO INSERT INTO
users (id, email_address, hashed_password, email_address_verified, fullname, saml_subject, created_at, updated_at) identities (id, email_address, hashed_password, email_address_verified, fullname, saml_subject, created_at, updated_at)
VALUES ( VALUES (
@user_id, @identity_id,
@email_address, @email_address,
@hashed_password, @hashed_password,
@email_address_verified, @email_address_verified,
@@ -239,14 +239,14 @@ VALUES (
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"user_id": u.ID, "identity_id": i.ID,
"email_address": u.EmailAddress, "email_address": i.EmailAddress,
"hashed_password": u.HashedPassword, "hashed_password": i.HashedPassword,
"fullname": u.FullName, "fullname": i.FullName,
"saml_subject": u.SAMLSubject, "saml_subject": i.SAMLSubject,
"created_at": u.CreatedAt, "created_at": i.CreatedAt,
"updated_at": u.UpdatedAt, "updated_at": i.UpdatedAt,
"email_address_verified": u.EmailAddressVerified, "email_address_verified": i.EmailAddressVerified,
} }
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
@@ -265,10 +265,10 @@ VALUES (
return nil return nil
} }
func (u *User) Update(ctx context.Context, conn pg.Conn) error { func (i *Identity) Update(ctx context.Context, conn pg.Conn) error {
q := ` q := `
UPDATE UPDATE
users identities
SET SET
email_address = @email_address, email_address = @email_address,
email_address_verified = @email_address_verified, email_address_verified = @email_address_verified,
@@ -277,22 +277,22 @@ SET
hashed_password = @hashed_password, hashed_password = @hashed_password,
updated_at = @updated_at updated_at = @updated_at
WHERE WHERE
id = @user_id id = @identity_id
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"user_id": u.ID, "identity_id": i.ID,
"email_address": u.EmailAddress, "email_address": i.EmailAddress,
"email_address_verified": u.EmailAddressVerified, "email_address_verified": i.EmailAddressVerified,
"saml_subject": u.SAMLSubject, "saml_subject": i.SAMLSubject,
"updated_at": u.UpdatedAt, "updated_at": i.UpdatedAt,
"fullname": u.FullName, "fullname": i.FullName,
"hashed_password": u.HashedPassword, "hashed_password": i.HashedPassword,
} }
result, err := conn.Exec(ctx, q, args) result, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot update user: %w", err) return fmt.Errorf("cannot update identity: %w", err)
} }
if result.RowsAffected() == 0 { if result.RowsAffected() == 0 {
@@ -302,8 +302,8 @@ WHERE
return nil return nil
} }
// LoadBySAMLSubject loads a user by their SAML subject (NameID) // LoadBySAMLSubject loads an identity by their SAML subject (NameID)
func (u *User) LoadBySAMLSubject( func (i *Identity) LoadBySAMLSubject(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
samlSubject string, samlSubject string,
@@ -319,7 +319,7 @@ SELECT
created_at, created_at,
updated_at updated_at
FROM FROM
users identities
WHERE WHERE
saml_subject = @saml_subject saml_subject = @saml_subject
LIMIT 1; LIMIT 1;
@@ -329,24 +329,24 @@ LIMIT 1;
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user by SAML subject: %w", err) return fmt.Errorf("cannot query identity by SAML subject: %w", err)
} }
user, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[User]) identity, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Identity])
if err != nil { if err != nil {
if errors.Is(err, pgx.ErrNoRows) { if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound return ErrResourceNotFound
} }
return fmt.Errorf("cannot collect user: %w", err) return fmt.Errorf("cannot collect identity: %w", err)
} }
*u = user *i = identity
return nil return nil
} }
func (u *User) CountMemberships( func (i *Identity) CountMemberships(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
) (int, error) { ) (int, error) {
@@ -356,19 +356,16 @@ SELECT
FROM FROM
authz_memberships authz_memberships
WHERE WHERE
user_id = @user_id identity_id = @identity_id
` `
args := pgx.StrictNamedArgs{"user_id": u.ID} args := pgx.StrictNamedArgs{"identity_id": i.ID}
var count int var count int
err := conn.QueryRow(ctx, q, args).Scan(&count) err := conn.QueryRow(ctx, q, args).Scan(&count)
if err != nil { if err != nil {
return 0, fmt.Errorf("cannot count user memberships: %w", err) return 0, fmt.Errorf("cannot count identity memberships: %w", err)
} }
return count, nil return count, nil
} }
// ConvertToTenantUser method removed
// All users are now global (no tenant conversion needed)

View File

@@ -15,26 +15,26 @@
package coredata package coredata
type ( type (
UserOrderField string IdentityOrderField string
) )
const ( const (
UserOrderFieldCreatedAt UserOrderField = "CREATED_AT" IdentityOrderFieldCreatedAt IdentityOrderField = "CREATED_AT"
) )
func (p UserOrderField) Column() string { func (p IdentityOrderField) Column() string {
return string(p) return string(p)
} }
func (p UserOrderField) String() string { func (p IdentityOrderField) String() string {
return string(p) return string(p)
} }
func (p UserOrderField) MarshalText() ([]byte, error) { func (p IdentityOrderField) MarshalText() ([]byte, error) {
return []byte(p.String()), nil return []byte(p.String()), nil
} }
func (p *UserOrderField) UnmarshalText(text []byte) error { func (p *IdentityOrderField) UnmarshalText(text []byte) error {
*p = UserOrderField(text) *p = IdentityOrderField(text)
return nil return nil
} }

View File

@@ -33,7 +33,7 @@ import (
type ( type (
Membership struct { Membership struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
UserID gid.GID `db:"user_id"` IdentityID gid.GID `db:"identity_id"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
Role MembershipRole `db:"role"` Role MembershipRole `db:"role"`
FullName string `db:"full_name"` FullName string `db:"full_name"`
@@ -60,11 +60,11 @@ func (m Membership) CursorKey(orderBy MembershipOrderField) page.CursorKey {
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (m *Membership) LoadByUserInOrganization(ctx context.Context, conn pg.Conn, userID gid.GID, organizationID gid.GID) error { func (m *Membership) LoadByIdentityInOrganization(ctx context.Context, conn pg.Conn, identityID gid.GID, organizationID gid.GID) error {
q := ` q := `
SELECT SELECT
id, id,
user_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
@@ -72,12 +72,12 @@ SELECT
FROM FROM
authz_memberships authz_memberships
WHERE WHERE
user_id = @user_id identity_id = @identity_id
AND organization_id = @organization_id AND organization_id = @organization_id
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"user_id": userID, "identity_id": identityID,
"organization_id": organizationID, "organization_id": organizationID,
} }
@@ -105,7 +105,7 @@ INSERT INTO
authz_memberships ( authz_memberships (
tenant_id, tenant_id,
id, id,
user_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
@@ -114,7 +114,7 @@ INSERT INTO
VALUES ( VALUES (
@tenant_id, @tenant_id,
@id, @id,
@user_id, @identity_id,
@organization_id, @organization_id,
@role, @role,
@created_at, @created_at,
@@ -125,7 +125,7 @@ VALUES (
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(), "tenant_id": scope.GetTenantID(),
"id": m.ID, "id": m.ID,
"user_id": m.UserID, "identity_id": m.IdentityID,
"organization_id": m.OrganizationID, "organization_id": m.OrganizationID,
"role": m.Role, "role": m.Role,
"created_at": m.CreatedAt, "created_at": m.CreatedAt,
@@ -159,7 +159,7 @@ func (m *Membership) LoadByID(
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
id, id,
user_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
@@ -172,17 +172,17 @@ WITH mbr AS (
) )
SELECT SELECT
mbr.id, mbr.id,
mbr.user_id, mbr.identity_id,
mbr.organization_id, mbr.organization_id,
mbr.role, mbr.role,
u.fullname as full_name, i.fullname as full_name,
u.email_address, i.email_address,
mbr.created_at, mbr.created_at,
mbr.updated_at mbr.updated_at
FROM FROM
mbr mbr
JOIN JOIN
users u ON mbr.user_id = u.id identities i ON mbr.identity_id = i.id
` `
query = fmt.Sprintf(query, scope.SQLFragment()) query = fmt.Sprintf(query, scope.SQLFragment())
@@ -210,19 +210,19 @@ JOIN
return nil return nil
} }
// LoadRoleByUserAndEntityID loads a user's role by querying any entity to extract its organization_id // LoadRoleByIdentityAndEntityID loads an identity's role by querying any entity to extract its organization_id
func (m *Membership) LoadRoleByUserAndEntityID( func (m *Membership) LoadRoleByIdentityAndEntityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
userID gid.GID, identityID gid.GID,
entityID gid.GID, entityID gid.GID,
) error { ) error {
entityType := entityID.EntityType() entityType := entityID.EntityType()
// For organization, the entity ID is the organization ID // For organization, the entity ID is the organization ID
if entityType == OrganizationEntityType { if entityType == OrganizationEntityType {
return m.LoadByUserAndOrg(ctx, conn, scope, userID, entityID) return m.LoadByIdentityAndOrg(ctx, conn, scope, identityID, entityID)
} }
tableName, ok := EntityTable(entityType) tableName, ok := EntityTable(entityType)
@@ -238,7 +238,7 @@ func (m *Membership) LoadRoleByUserAndEntityID(
query := fmt.Sprintf(` query := fmt.Sprintf(`
SELECT SELECT
m.id, m.id,
m.user_id, m.identity_id,
m.organization_id, m.organization_id,
m.role, m.role,
m.created_at, m.created_at,
@@ -248,14 +248,14 @@ FROM
INNER JOIN %s e ON e.id = @entity_id INNER JOIN %s e ON e.id = @entity_id
WHERE WHERE
%s %s
AND m.user_id = @user_id AND m.identity_id = @identity_id
AND m.organization_id = e.organization_id AND m.organization_id = e.organization_id
LIMIT 1; LIMIT 1;
`, tableName, scopeFragment) `, tableName, scopeFragment)
args := pgx.NamedArgs{ args := pgx.NamedArgs{
"user_id": userID, "identity_id": identityID,
"entity_id": entityID, "entity_id": entityID,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
@@ -272,7 +272,7 @@ LIMIT 1;
var membership Membership var membership Membership
err = rows.Scan( err = rows.Scan(
&membership.ID, &membership.ID,
&membership.UserID, &membership.IdentityID,
&membership.OrganizationID, &membership.OrganizationID,
&membership.Role, &membership.Role,
&membership.CreatedAt, &membership.CreatedAt,
@@ -287,18 +287,18 @@ LIMIT 1;
return nil return nil
} }
func (m *Membership) LoadByUserAndOrg( func (m *Membership) LoadByIdentityAndOrg(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
userID gid.GID, identityID gid.GID,
organizationID gid.GID, organizationID gid.GID,
) error { ) error {
q := ` q := `
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
am.id, am.id,
am.user_id, am.identity_id,
am.organization_id, am.organization_id,
am.role, am.role,
am.created_at, am.created_at,
@@ -306,29 +306,29 @@ WITH mbr AS (
FROM FROM
authz_memberships am authz_memberships am
WHERE WHERE
am.user_id = @user_id am.identity_id = @identity_id
AND am.organization_id = @organization_id AND am.organization_id = @organization_id
AND %s AND %s
) )
SELECT SELECT
mbr.id, mbr.id,
mbr.user_id, mbr.identity_id,
mbr.organization_id, mbr.organization_id,
mbr.role, mbr.role,
u.fullname as full_name, i.fullname as full_name,
u.email_address, i.email_address,
mbr.created_at, mbr.created_at,
mbr.updated_at mbr.updated_at
FROM FROM
mbr mbr
JOIN JOIN
users u ON mbr.user_id = u.id identities i ON mbr.identity_id = i.id
` `
q = fmt.Sprintf(q, scope.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"user_id": userID, "identity_id": identityID,
"organization_id": organizationID, "organization_id": organizationID,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
@@ -412,18 +412,18 @@ WHERE
return nil return nil
} }
func (m *Memberships) LoadByUserID( func (m *Memberships) LoadByIdentityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
userID gid.GID, identityID gid.GID,
cursor *page.Cursor[MembershipOrderField], cursor *page.Cursor[MembershipOrderField],
) error { ) error {
query := ` query := `
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
id, id,
user_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
@@ -431,24 +431,24 @@ WITH mbr AS (
FROM FROM
authz_memberships authz_memberships
WHERE WHERE
user_id = @user_id identity_id = @identity_id
AND %s AND %s
ORDER BY ORDER BY
created_at DESC created_at DESC
) )
SELECT SELECT
mbr.id, mbr.id,
mbr.user_id, mbr.identity_id,
mbr.organization_id, mbr.organization_id,
mbr.role, mbr.role,
u.fullname as full_name, i.fullname as full_name,
u.email_address, i.email_address,
mbr.created_at, mbr.created_at,
mbr.updated_at mbr.updated_at
FROM FROM
mbr mbr
JOIN JOIN
users u ON mbr.user_id = u.id identities i ON mbr.identity_id = i.id
ORDER BY ORDER BY
mbr.created_at DESC mbr.created_at DESC
` `
@@ -456,7 +456,7 @@ ORDER BY
query = fmt.Sprintf(query, scope.SQLFragment()) query = fmt.Sprintf(query, scope.SQLFragment())
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"user_id": userID, "identity_id": identityID,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
@@ -485,7 +485,7 @@ func (m *Memberships) LoadByOrganizationID(
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
id, id,
user_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
@@ -498,7 +498,7 @@ WITH mbr AS (
) )
SELECT SELECT
id, id,
user_id, identity_id,
organization_id, organization_id,
role, role,
full_name, full_name,
@@ -508,18 +508,18 @@ SELECT
FROM ( FROM (
SELECT SELECT
mbr.id, mbr.id,
mbr.user_id, mbr.identity_id,
mbr.organization_id, mbr.organization_id,
mbr.role, mbr.role,
u.fullname as full_name, i.fullname as full_name,
u.email_address, i.email_address,
mbr.created_at, mbr.created_at,
mbr.updated_at mbr.updated_at
FROM FROM
mbr mbr
JOIN JOIN
users u ON mbr.user_id = u.id identities i ON mbr.identity_id = i.id
) AS membership_with_user ) AS membership_with_identity
WHERE %s WHERE %s
` `
@@ -573,10 +573,10 @@ WHERE
return count, nil return count, nil
} }
func (m *Memberships) CountByUserID( func (m *Memberships) CountByIdentityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
userID gid.GID, identityID gid.GID,
) (int, error) { ) (int, error) {
query := ` query := `
SELECT SELECT
@@ -584,10 +584,10 @@ SELECT
FROM FROM
authz_memberships authz_memberships
WHERE WHERE
user_id = @user_id identity_id = @identity_id
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"user_id": userID, "identity_id": identityID,
} }
row := conn.QueryRow(ctx, query, args) row := conn.QueryRow(ctx, query, args)

View File

@@ -0,0 +1,8 @@
ALTER TABLE users RENAME TO identities;
ALTER TABLE sessions RENAME COLUMN user_id TO identity_id;
ALTER TABLE authz_memberships RENAME COLUMN user_id TO identity_id;
ALTER TABLE peoples RENAME COLUMN user_id TO identity_id;
ALTER TABLE auth_user_api_keys RENAME TO auth_personal_api_keys;
ALTER TABLE auth_personal_api_keys RENAME COLUMN user_id TO identity_id;
ALTER TABLE authz_api_keys_memberships RENAME COLUMN auth_user_api_key_id TO auth_personal_api_key_id;

View File

@@ -111,21 +111,21 @@ LIMIT 1;
return nil return nil
} }
func (o *Organizations) LoadByUserID( func (o *Organizations) LoadByIdentityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
userID gid.GID, identityID gid.GID,
cursor *page.Cursor[OrganizationOrderField], cursor *page.Cursor[OrganizationOrderField],
) error { ) error {
q := ` q := `
WITH user_org AS ( WITH identity_org AS (
SELECT SELECT
organization_id organization_id
FROM FROM
authz_memberships authz_memberships
WHERE WHERE
user_id = @user_id identity_id = @identity_id
) )
SELECT SELECT
tenant_id, tenant_id,
@@ -143,7 +143,7 @@ SELECT
FROM FROM
organizations organizations
INNER JOIN INNER JOIN
user_org ON organizations.id = user_org.organization_id identity_org ON organizations.id = identity_org.organization_id
WHERE WHERE
%s %s
AND %s AND %s
@@ -151,7 +151,7 @@ WHERE
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"user_id": userID} args := pgx.StrictNamedArgs{"identity_id": identityID}
maps.Copy(args, cursor.SQLArguments()) maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
@@ -169,19 +169,19 @@ WHERE
return nil return nil
} }
func (o *Organizations) LoadAllByUserID( func (o *Organizations) LoadAllByIdentityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
userID gid.GID, identityID gid.GID,
) error { ) error {
q := ` q := `
WITH user_org AS ( WITH identity_org AS (
SELECT SELECT
organization_id organization_id
FROM FROM
authz_memberships authz_memberships
WHERE WHERE
user_id = @user_id identity_id = @identity_id
) )
SELECT SELECT
tenant_id, tenant_id,
@@ -199,12 +199,12 @@ SELECT
FROM FROM
organizations organizations
INNER JOIN INNER JOIN
user_org ON organizations.id = user_org.organization_id identity_org ON organizations.id = identity_org.organization_id
ORDER BY ORDER BY
name ASC name ASC
` `
args := pgx.StrictNamedArgs{"user_id": userID} args := pgx.StrictNamedArgs{"identity_id": identityID}
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
@@ -221,20 +221,20 @@ ORDER BY
return nil return nil
} }
func (o *Organizations) LoadAllByUserIDWithRole( func (o *Organizations) LoadAllByIdentityIDWithRole(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
userID gid.GID, identityID gid.GID,
role MembershipRole, role MembershipRole,
) error { ) error {
q := ` q := `
WITH user_org AS ( WITH identity_org AS (
SELECT SELECT
organization_id organization_id
FROM FROM
authz_memberships authz_memberships
WHERE WHERE
user_id = @user_id identity_id = @identity_id
AND role = @role AND role = @role
) )
SELECT SELECT
@@ -253,14 +253,14 @@ SELECT
FROM FROM
organizations organizations
INNER JOIN INNER JOIN
user_org ON organizations.id = user_org.organization_id identity_org ON organizations.id = identity_org.organization_id
ORDER BY ORDER BY
name ASC name ASC
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"user_id": userID, "identity_id": identityID,
"role": role, "role": role,
} }
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
@@ -278,13 +278,13 @@ ORDER BY
return nil return nil
} }
func (o *Organizations) LoadAllByUserAPIKeyID( func (o *Organizations) LoadAllByPersonalAPIKeyID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
userAPIKeyID gid.GID, personalAPIKeyID gid.GID,
) error { ) error {
q := ` q := `
WITH user_api_key_org AS ( WITH personal_api_key_org AS (
SELECT SELECT
am.organization_id am.organization_id
FROM FROM
@@ -292,7 +292,7 @@ WITH user_api_key_org AS (
INNER JOIN INNER JOIN
authz_memberships am ON akm.membership_id = am.id authz_memberships am ON akm.membership_id = am.id
WHERE WHERE
akm.auth_user_api_key_id = @auth_user_api_key_id akm.auth_personal_api_key_id = @auth_personal_api_key_id
) )
SELECT SELECT
tenant_id, tenant_id,
@@ -310,12 +310,12 @@ SELECT
FROM FROM
organizations organizations
INNER JOIN INNER JOIN
user_api_key_org ON organizations.id = user_api_key_org.organization_id personal_api_key_org ON organizations.id = personal_api_key_org.organization_id
ORDER BY ORDER BY
name ASC name ASC
` `
args := pgx.StrictNamedArgs{"auth_user_api_key_id": userAPIKeyID} args := pgx.StrictNamedArgs{"auth_personal_api_key_id": personalAPIKeyID}
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {

View File

@@ -27,9 +27,9 @@ import (
) )
type ( type (
UserAPIKey struct { PersonalAPIKey struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
UserID gid.GID `db:"user_id"` IdentityID gid.GID `db:"identity_id"`
Name string `db:"name"` Name string `db:"name"`
ExpiresAt time.Time `db:"expires_at"` ExpiresAt time.Time `db:"expires_at"`
ExpireReason *ExpireReason `db:"expire_reason"` ExpireReason *ExpireReason `db:"expire_reason"`
@@ -37,19 +37,19 @@ type (
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
} }
UserAPIKeys []*UserAPIKey PersonalAPIKeys []*PersonalAPIKey
) )
func (a *UserAPIKey) CursorKey(orderBy UserAPIKeyOrderField) page.CursorKey { func (a *PersonalAPIKey) CursorKey(orderBy PersonalAPIKeyOrderField) page.CursorKey {
switch orderBy { switch orderBy {
case UserAPIKeyOrderFieldCreatedAt: case PersonalAPIKeyOrderFieldCreatedAt:
return page.NewCursorKey(a.ID, a.CreatedAt) return page.NewCursorKey(a.ID, a.CreatedAt)
} }
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
} }
func (a *UserAPIKey) LoadByID( func (a *PersonalAPIKey) LoadByID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
apiKeyID gid.GID, apiKeyID gid.GID,
@@ -57,14 +57,14 @@ func (a *UserAPIKey) LoadByID(
q := ` q := `
SELECT SELECT
id, id,
user_id, identity_id,
name, name,
expires_at, expires_at,
expire_reason, expire_reason,
created_at, created_at,
updated_at updated_at
FROM FROM
auth_user_api_keys auth_personal_api_keys
WHERE WHERE
id = @api_key_id id = @api_key_id
LIMIT 1; LIMIT 1;
@@ -74,16 +74,16 @@ LIMIT 1;
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user api key: %w", err) return fmt.Errorf("cannot query personal api key: %w", err)
} }
apiKey, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[UserAPIKey]) apiKey, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PersonalAPIKey])
if err != nil { if err != nil {
if errors.Is(err, pgx.ErrNoRows) { if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound return ErrResourceNotFound
} }
return fmt.Errorf("cannot collect user api key: %w", err) return fmt.Errorf("cannot collect personal api key: %w", err)
} }
*a = apiKey *a = apiKey
@@ -91,37 +91,37 @@ LIMIT 1;
return nil return nil
} }
func (a *UserAPIKeys) LoadByUserID( func (a *PersonalAPIKeys) LoadByIdentityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
userID gid.GID, identityID gid.GID,
) error { ) error {
q := ` q := `
SELECT SELECT
id, id,
user_id, identity_id,
name, name,
expires_at, expires_at,
expire_reason, expire_reason,
created_at, created_at,
updated_at updated_at
FROM FROM
auth_user_api_keys auth_personal_api_keys
WHERE WHERE
user_id = @user_id identity_id = @identity_id
ORDER BY created_at DESC; ORDER BY created_at DESC;
` `
args := pgx.StrictNamedArgs{"user_id": userID} args := pgx.StrictNamedArgs{"identity_id": identityID}
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot query user api keys: %w", err) return fmt.Errorf("cannot query personal api keys: %w", err)
} }
apiKeys, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[UserAPIKey]) apiKeys, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PersonalAPIKey])
if err != nil { if err != nil {
return fmt.Errorf("cannot collect user api keys: %w", err) return fmt.Errorf("cannot collect personal api keys: %w", err)
} }
*a = apiKeys *a = apiKeys
@@ -129,18 +129,18 @@ ORDER BY created_at DESC;
return nil return nil
} }
func (a *UserAPIKeys) CountByUserID(ctx context.Context, conn pg.Conn, userID gid.GID) (int, error) { func (a *PersonalAPIKeys) CountByIdentityID(ctx context.Context, conn pg.Conn, identityID gid.GID) (int, error) {
q := ` q := `
SELECT SELECT
COUNT(*) COUNT(*)
FROM FROM
auth_user_api_keys auth_personal_api_keys
WHERE WHERE
user_id = @user_id identity_id = @identity_id
ORDER BY created_at DESC; ORDER BY created_at DESC;
` `
args := pgx.StrictNamedArgs{"user_id": userID} args := pgx.StrictNamedArgs{"identity_id": identityID}
row := conn.QueryRow(ctx, q, args) row := conn.QueryRow(ctx, q, args)
var count int var count int
if err := row.Scan(&count); err != nil { if err := row.Scan(&count); err != nil {
@@ -150,16 +150,16 @@ ORDER BY created_at DESC;
return count, nil return count, nil
} }
func (a *UserAPIKey) Insert( func (a *PersonalAPIKey) Insert(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
) error { ) error {
q := ` q := `
INSERT INTO INSERT INTO
auth_user_api_keys (id, user_id, name, expires_at, expire_reason, created_at, updated_at) auth_personal_api_keys (id, identity_id, name, expires_at, expire_reason, created_at, updated_at)
VALUES ( VALUES (
@api_key_id, @api_key_id,
@user_id, @identity_id,
@name, @name,
@expires_at, @expires_at,
@expire_reason, @expire_reason,
@@ -170,7 +170,7 @@ VALUES (
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"api_key_id": a.ID, "api_key_id": a.ID,
"user_id": a.UserID, "identity_id": a.IdentityID,
"name": a.Name, "name": a.Name,
"expires_at": a.ExpiresAt, "expires_at": a.ExpiresAt,
"expire_reason": a.ExpireReason, "expire_reason": a.ExpireReason,
@@ -180,19 +180,19 @@ VALUES (
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot insert user api key: %w", err) return fmt.Errorf("cannot insert personal api key: %w", err)
} }
return nil return nil
} }
func (a *UserAPIKey) Update( func (a *PersonalAPIKey) Update(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
) error { ) error {
q := ` q := `
UPDATE UPDATE
auth_user_api_keys auth_personal_api_keys
SET SET
name = @name, name = @name,
expires_at = @expires_at, expires_at = @expires_at,
@@ -212,19 +212,19 @@ WHERE
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot update user api key: %w", err) return fmt.Errorf("cannot update personal api key: %w", err)
} }
return nil return nil
} }
func (a *UserAPIKey) Delete( func (a *PersonalAPIKey) Delete(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
) error { ) error {
q := ` q := `
DELETE FROM DELETE FROM
auth_user_api_keys auth_personal_api_keys
WHERE WHERE
id = @api_key_id id = @api_key_id
` `
@@ -233,7 +233,7 @@ WHERE
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
if err != nil { if err != nil {
return fmt.Errorf("cannot delete user api key: %w", err) return fmt.Errorf("cannot delete personal api key: %w", err)
} }
return nil return nil

View File

@@ -15,26 +15,26 @@
package coredata package coredata
type ( type (
UserAPIKeyOrderField string PersonalAPIKeyOrderField string
) )
const ( const (
UserAPIKeyOrderFieldCreatedAt UserAPIKeyOrderField = "CREATED_AT" PersonalAPIKeyOrderFieldCreatedAt PersonalAPIKeyOrderField = "CREATED_AT"
) )
func (p UserAPIKeyOrderField) Column() string { func (p PersonalAPIKeyOrderField) Column() string {
return string(p) return string(p)
} }
func (p UserAPIKeyOrderField) String() string { func (p PersonalAPIKeyOrderField) String() string {
return string(p) return string(p)
} }
func (p UserAPIKeyOrderField) MarshalText() ([]byte, error) { func (p PersonalAPIKeyOrderField) MarshalText() ([]byte, error) {
return []byte(p.String()), nil return []byte(p.String()), nil
} }
func (p *UserAPIKeyOrderField) UnmarshalText(text []byte) error { func (p *PersonalAPIKeyOrderField) UnmarshalText(text []byte) error {
*p = UserAPIKeyOrderField(text) *p = PersonalAPIKeyOrderField(text)
return nil return nil
} }

View File

@@ -31,7 +31,7 @@ import (
type ( type (
Session struct { Session struct {
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
UserID gid.GID `db:"user_id"` IdentityID gid.GID `db:"identity_id"`
TenantID *gid.TenantID `db:"tenant_id"` TenantID *gid.TenantID `db:"tenant_id"`
MembershipID *gid.GID `db:"membership_id"` MembershipID *gid.GID `db:"membership_id"`
ParentSessionID *gid.GID `db:"parent_session_id"` ParentSessionID *gid.GID `db:"parent_session_id"`
@@ -58,11 +58,11 @@ const (
AuthMethodSAML AuthMethod = "SAML" AuthMethodSAML AuthMethod = "SAML"
) )
func NewRootSession(userID gid.GID, method AuthMethod, duration time.Duration) *Session { func NewRootSession(identityID gid.GID, method AuthMethod, duration time.Duration) *Session {
now := time.Now() now := time.Now()
return &Session{ return &Session{
ID: gid.New(gid.NilTenant, SessionEntityType), ID: gid.New(gid.NilTenant, SessionEntityType),
UserID: userID, IdentityID: identityID,
ExpiredAt: now.Add(duration), ExpiredAt: now.Add(duration),
AuthMethod: method, AuthMethod: method,
AuthenticatedAt: now, AuthenticatedAt: now,
@@ -100,7 +100,7 @@ func (s *Session) LoadByID(
q := ` q := `
SELECT SELECT
id, id,
user_id, identity_id,
tenant_id, tenant_id,
membership_id, membership_id,
data, data,
@@ -146,10 +146,10 @@ func (s *Session) Insert(
) error { ) error {
q := ` q := `
INSERT INTO INSERT INTO
sessions (id, user_id, tenant_id, membership_id, data, parent_session_id, auth_method, authenticated_at, expire_reason, user_agent, ip_address, expired_at, created_at, updated_at) sessions (id, identity_id, tenant_id, membership_id, data, parent_session_id, auth_method, authenticated_at, expire_reason, user_agent, ip_address, expired_at, created_at, updated_at)
VALUES ( VALUES (
@session_id, @session_id,
@user_id, @identity_id,
@tenant_id, @tenant_id,
@membership_id, @membership_id,
@data, @data,
@@ -167,7 +167,7 @@ VALUES (
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"session_id": s.ID, "session_id": s.ID,
"user_id": s.UserID, "identity_id": s.IdentityID,
"tenant_id": s.TenantID, "tenant_id": s.TenantID,
"membership_id": s.MembershipID, "membership_id": s.MembershipID,
"data": s.Data, "data": s.Data,
@@ -225,11 +225,11 @@ WHERE
return nil return nil
} }
func (s *Sessions) LoadByUserID(ctx context.Context, conn pg.Conn, userID gid.GID, cursor *page.Cursor[SessionOrderField]) error { func (s *Sessions) LoadByIdentityID(ctx context.Context, conn pg.Conn, identityID gid.GID, cursor *page.Cursor[SessionOrderField]) error {
q := ` q := `
SELECT SELECT
id, id,
user_id, identity_id,
tenant_id, tenant_id,
membership_id, membership_id,
data, data,
@@ -245,13 +245,13 @@ SELECT
FROM FROM
sessions sessions
WHERE WHERE
user_id = @user_id identity_id = @identity_id
AND %s AND %s
` `
q = fmt.Sprintf(q, cursor.SQLFragment()) q = fmt.Sprintf(q, cursor.SQLFragment())
args := pgx.StrictNamedArgs{"user_id": userID} args := pgx.StrictNamedArgs{"identity_id": identityID}
maps.Copy(args, cursor.SQLArguments()) maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args) rows, err := conn.Query(ctx, q, args)
@@ -269,17 +269,17 @@ WHERE
return nil return nil
} }
func (s *Sessions) CountByUserID(ctx context.Context, conn pg.Conn, userID gid.GID) (int, error) { func (s *Sessions) CountByIdentityID(ctx context.Context, conn pg.Conn, identityID gid.GID) (int, error) {
q := ` q := `
SELECT SELECT
COUNT(*) COUNT(*)
FROM FROM
sessions sessions
WHERE WHERE
user_id = @user_id identity_id = @identity_id
` `
args := pgx.StrictNamedArgs{"user_id": userID} args := pgx.StrictNamedArgs{"identity_id": identityID}
row := conn.QueryRow(ctx, q, args) row := conn.QueryRow(ctx, q, args)
@@ -291,7 +291,7 @@ WHERE
return count, nil return count, nil
} }
func (s *Sessions) ExpireAllForUserExceptOneSession(ctx context.Context, conn pg.Conn, userID gid.GID, sessionID gid.GID) (int64, error) { func (s *Sessions) ExpireAllForIdentityExceptOneSession(ctx context.Context, conn pg.Conn, identityID gid.GID, sessionID gid.GID) (int64, error) {
q := ` q := `
UPDATE sessions UPDATE sessions
SET SET
@@ -300,13 +300,13 @@ SET
expire_reason = 'revoked' expire_reason = 'revoked'
WHERE WHERE
id != @session_id id != @session_id
AND user_id = @user_id AND identity_id = @identity_id
AND expire_reason IS NULL AND expire_reason IS NULL
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"session_id": sessionID, "session_id": sessionID,
"user_id": userID, "identity_id": identityID,
} }
result, err := conn.Exec(ctx, q, args) result, err := conn.Exec(ctx, q, args)
@@ -321,7 +321,7 @@ func (s *Session) LoadByRootSessionIDAndMembershipID(ctx context.Context, conn p
q := ` q := `
SELECT SELECT
id, id,
user_id, identity_id,
tenant_id, tenant_id,
membership_id, membership_id,
data, data,

View File

@@ -38,12 +38,12 @@ func NewAccessManagementService(svc *Service) *AccessManagementService {
} }
// Authorize implements Model 2 authorization: // Authorize implements Model 2 authorization:
// - principalID is the actor (User now; later service accounts) // - principalID is the actor (Identity now; later service accounts)
// - credentialID is an optional credential (UserAPIKey now) // - credentialID is an optional credential (PersonalAPIKey now)
// - intersection semantics: actor must be allowed AND credential (if present) must be allowed. // - intersection semantics: actor must be allowed AND credential (if present) must be allowed.
// //
// Entity scope: // Entity scope:
// - Global/self-owned entities (User/Session/UserAPIKey) are authorized via ownership checks only (no global admin). // - Global/self-owned entities (Identity/Session/PersonalAPIKey) are authorized via ownership checks only (no global admin).
// - Organization-scoped entities are authorized via membership lookups that derive organization_id from entityID. // - Organization-scoped entities are authorized via membership lookups that derive organization_id from entityID.
func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid.GID, credentialID *gid.GID, entityID gid.GID, action Action) error { func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid.GID, credentialID *gid.GID, entityID gid.GID, action Action) error {
requiredRoles := GetPermissionsForAction(entityID.EntityType(), action) requiredRoles := GetPermissionsForAction(entityID.EntityType(), action)
@@ -53,7 +53,7 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
} }
switch principalID.EntityType() { switch principalID.EntityType() {
case coredata.UserEntityType: case coredata.IdentityEntityType:
// ok // ok
default: default:
return NewUnsupportedPrincipalTypeError(principalID.EntityType()) return NewUnsupportedPrincipalTypeError(principalID.EntityType())
@@ -62,7 +62,7 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
return s.pg.WithConn(ctx, func(conn pg.Conn) error { return s.pg.WithConn(ctx, func(conn pg.Conn) error {
// Global/self-owned path // Global/self-owned path
switch entityID.EntityType() { switch entityID.EntityType() {
case coredata.UserEntityType: case coredata.IdentityEntityType:
if entityID != principalID { if entityID != principalID {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
@@ -73,17 +73,17 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
if err := sess.LoadByID(ctx, conn, entityID); err != nil { if err := sess.LoadByID(ctx, conn, entityID); err != nil {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
if sess.UserID != principalID { if sess.IdentityID != principalID {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
return nil return nil
case coredata.UserAPIKeyEntityType: case coredata.PersonalAPIKeyEntityType:
key := &coredata.UserAPIKey{} key := &coredata.PersonalAPIKey{}
if err := key.LoadByID(ctx, conn, entityID); err != nil { if err := key.LoadByID(ctx, conn, entityID); err != nil {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
if key.UserID != principalID { if key.IdentityID != principalID {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
return nil return nil
@@ -92,7 +92,7 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
// Organization-scoped path (derive org via joins) // Organization-scoped path (derive org via joins)
scope := coredata.NewScope(entityID.TenantID()) scope := coredata.NewScope(entityID.TenantID())
actorRoleName, err := s.loadUserRoleForEntity(ctx, conn, scope, principalID, entityID) actorRoleName, err := s.loadIdentityRoleForEntity(ctx, conn, scope, principalID, entityID)
if err != nil || !requiredRoleNamesContain(actorRoleName, requiredRoles) { if err != nil || !requiredRoleNamesContain(actorRoleName, requiredRoles) {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
@@ -100,13 +100,13 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
// Optional credential restriction (intersection) // Optional credential restriction (intersection)
if credentialID != nil { if credentialID != nil {
switch credentialID.EntityType() { switch credentialID.EntityType() {
case coredata.UserAPIKeyEntityType: case coredata.PersonalAPIKeyEntityType:
// Defensive check: credential must belong to actor // Defensive check: credential must belong to actor
apiKey := &coredata.UserAPIKey{} apiKey := &coredata.PersonalAPIKey{}
if err := apiKey.LoadByID(ctx, conn, *credentialID); err != nil { if err := apiKey.LoadByID(ctx, conn, *credentialID); err != nil {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
if apiKey.UserID != principalID { if apiKey.IdentityID != principalID {
return NewInsufficientPermissionsError(principalID, entityID, action) return NewInsufficientPermissionsError(principalID, entityID, action)
} }
@@ -123,15 +123,15 @@ func (s *AccessManagementService) Authorize(ctx context.Context, principalID gid
}) })
} }
func (s *AccessManagementService) loadUserRoleForEntity( func (s *AccessManagementService) loadIdentityRoleForEntity(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope coredata.Scoper, scope coredata.Scoper,
userID gid.GID, identityID gid.GID,
entityID gid.GID, entityID gid.GID,
) (Role, error) { ) (Role, error) {
var m coredata.Membership var m coredata.Membership
if err := m.LoadRoleByUserAndEntityID(ctx, conn, scope, userID, entityID); err != nil { if err := m.LoadRoleByIdentityAndEntityID(ctx, conn, scope, identityID, entityID); err != nil {
// Do not leak existence details // Do not leak existence details
if errors.Is(err, coredata.ErrResourceNotFound) { if errors.Is(err, coredata.ErrResourceNotFound) {
return "", err return "", err
@@ -148,7 +148,7 @@ func (s *AccessManagementService) loadAPIKeyRoleForEntity(
apiKeyID gid.GID, apiKeyID gid.GID,
entityID gid.GID, entityID gid.GID,
) (Role, error) { ) (Role, error) {
var akm coredata.UserAPIKeyMembership var akm coredata.PersonalAPIKeyMembership
if err := akm.LoadRoleByAPIKeyAndEntityID(ctx, conn, scope, apiKeyID, entityID); err != nil { if err := akm.LoadRoleByAPIKeyAndEntityID(ctx, conn, scope, apiKeyID, entityID); err != nil {
return "", err return "", err
} }
@@ -173,35 +173,3 @@ func requiredRoleNamesContain(roleName Role, required []Role) bool {
} }
return false return false
} }
// func (s AccountService) AllAccessibleTenants(ctx context.Context, identityID gid.GID) ([]gid.TenantID, error) {
// var tenants []gid.TenantID
// err := s.pg.WithConn(
// ctx,
// func(conn pg.Conn) error {
// memberships := coredata.Memberships{}
// orderBy := page.OrderBy[coredata.MembershipOrderField]{
// Field: coredata.MembershipOrderFieldCreatedAt,
// Direction: page.OrderDirectionDesc,
// }
// cursor := page.NewCursor(1000, nil, page.Head, orderBy)
// err := memberships.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
// if err != nil {
// return fmt.Errorf("cannot load memberships: %w", err)
// }
// for _, membership := range memberships {
// tenants = append(tenants, membership.ID.TenantID())
// }
// return nil
// },
// )
// if err != nil {
// return nil, err
// }
// return tenants, nil
// }

View File

@@ -35,7 +35,7 @@ type (
*Service *Service
} }
UserAPIKeyTokenData struct { PersonalAPIKeyTokenData struct {
Version int `json:"v"` Version int `json:"v"`
KeyID gid.GID `json:"kid"` KeyID gid.GID `json:"kid"`
PrincipalID gid.GID `json:"pid"` PrincipalID gid.GID `json:"pid"`
@@ -43,8 +43,8 @@ type (
} }
EmailConfirmationData struct { EmailConfirmationData struct {
UserID gid.GID `json:"uid"` IdentityID gid.GID `json:"uid"`
Email mail.Addr `json:"email"` Email mail.Addr `json:"email"`
} }
) )
@@ -78,7 +78,7 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
s.tokenSecret, s.tokenSecret,
TokenTypeEmailConfirmation, TokenTypeEmailConfirmation,
24*time.Hour, 24*time.Hour,
EmailConfirmationData{UserID: identityID, Email: req.NewEmail}, EmailConfirmationData{IdentityID: identityID, Email: req.NewEmail},
) )
if err != nil { if err != nil {
return fmt.Errorf("cannot generate confirmation token: %w", err) return fmt.Errorf("cannot generate confirmation token: %w", err)
@@ -97,17 +97,17 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
return s.pg.WithTx( return s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
user := &coredata.User{} identity := &coredata.Identity{}
err := user.LoadByID(ctx, tx, identityID) err := identity.LoadByID(ctx, tx, identityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(identityID) return NewIdentityNotFoundError(identityID)
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.Password), user.HashedPassword) isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.Password), identity.HashedPassword)
if err != nil { if err != nil {
return fmt.Errorf("cannot compare password: %w", err) return fmt.Errorf("cannot compare password: %w", err)
} }
@@ -116,18 +116,18 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
return NewInvalidPasswordError("invalid password") return NewInvalidPasswordError("invalid password")
} }
user.EmailAddress = req.NewEmail identity.EmailAddress = req.NewEmail
user.EmailAddressVerified = false identity.EmailAddressVerified = false
user.UpdatedAt = time.Now() identity.UpdatedAt = time.Now()
err = user.Update(ctx, tx) err = identity.Update(ctx, tx)
if err != nil { if err != nil {
return fmt.Errorf("cannot update user: %w", err) return fmt.Errorf("cannot update identity: %w", err)
} }
subject, textBody, htmlBody, err := emails.RenderConfirmEmail( subject, textBody, htmlBody, err := emails.RenderConfirmEmail(
s.baseURL, s.baseURL,
user.FullName, identity.FullName,
confirmationUrl, confirmationUrl,
) )
if err != nil { if err != nil {
@@ -135,8 +135,8 @@ func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req
} }
confirmationEmail := coredata.NewEmail( confirmationEmail := coredata.NewEmail(
user.FullName, identity.FullName,
user.EmailAddress, identity.EmailAddress,
subject, subject,
textBody, textBody,
htmlBody, htmlBody,
@@ -161,30 +161,30 @@ func (s AccountService) VerifyEmail(ctx context.Context, token string) error {
return s.pg.WithTx( return s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
user := &coredata.User{} identity := &coredata.Identity{}
err := user.LoadByID(ctx, tx, payload.Data.UserID) err := identity.LoadByID(ctx, tx, payload.Data.IdentityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(payload.Data.UserID) return NewIdentityNotFoundError(payload.Data.IdentityID)
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
if user.EmailAddress != payload.Data.Email { if identity.EmailAddress != payload.Data.Email {
return NewEmailVerificationMismatchError() return NewEmailVerificationMismatchError()
} }
if user.EmailAddressVerified { if identity.EmailAddressVerified {
return NewEmailAlreadyVerifiedError() return NewEmailAlreadyVerifiedError()
} }
user.EmailAddressVerified = true identity.EmailAddressVerified = true
user.UpdatedAt = time.Now() identity.UpdatedAt = time.Now()
err = user.Update(ctx, tx) err = identity.Update(ctx, tx)
if err != nil { if err != nil {
return fmt.Errorf("cannot update user: %w", err) return fmt.Errorf("cannot update identity: %w", err)
} }
return nil return nil
@@ -205,16 +205,16 @@ func (s *AccountService) AcceptInvitation(
err := s.pg.WithTx( err := s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
user := coredata.User{} identity := coredata.Identity{}
invitation := coredata.Invitation{} invitation := coredata.Invitation{}
err := user.LoadByID(ctx, tx, identityID) err := identity.LoadByID(ctx, tx, identityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(identityID) return NewIdentityNotFoundError(identityID)
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
err = invitation.LoadByID(ctx, tx, coredata.NewNoScope(), invitationID) err = invitation.LoadByID(ctx, tx, coredata.NewNoScope(), invitationID)
@@ -226,7 +226,7 @@ func (s *AccountService) AcceptInvitation(
return fmt.Errorf("cannot load invitation: %w", err) return fmt.Errorf("cannot load invitation: %w", err)
} }
if invitation.Email != user.EmailAddress { if invitation.Email != identity.EmailAddress {
return NewInvitationNotFoundError(invitationID) return NewInvitationNotFoundError(invitationID)
} }
@@ -243,7 +243,7 @@ func (s *AccountService) AcceptInvitation(
membership = &coredata.Membership{ membership = &coredata.Membership{
ID: gid.New(tenantID, coredata.MembershipEntityType), ID: gid.New(tenantID, coredata.MembershipEntityType),
UserID: identityID, IdentityID: identityID,
OrganizationID: invitation.OrganizationID, OrganizationID: invitation.OrganizationID,
Role: invitation.Role, Role: invitation.Role,
CreatedAt: now, CreatedAt: now,
@@ -286,11 +286,11 @@ func (s *AccountService) ListPendingInvitations(
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
identity := coredata.User{} identity := coredata.Identity{}
err := identity.LoadByID(ctx, conn, identityID) err := identity.LoadByID(ctx, conn, identityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(identityID) return NewIdentityNotFoundError(identityID)
} }
return fmt.Errorf("cannot load identity: %w", err) return fmt.Errorf("cannot load identity: %w", err)
@@ -323,7 +323,7 @@ func (s *AccountService) CountPendingInvitations(
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
identity := coredata.User{} identity := coredata.Identity{}
err := identity.LoadByID(ctx, conn, identityID) err := identity.LoadByID(ctx, conn, identityID)
if err != nil { if err != nil {
return fmt.Errorf("cannot load identity: %w", err) return fmt.Errorf("cannot load identity: %w", err)
@@ -357,7 +357,7 @@ func (s *AccountService) ListMemberships(
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := memberships.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor) err := memberships.LoadByIdentityID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
if err != nil { if err != nil {
return fmt.Errorf("cannot load memberships: %w", err) return fmt.Errorf("cannot load memberships: %w", err)
} }
@@ -383,7 +383,7 @@ func (s *AccountService) CountMemberships(
ctx, ctx,
func(conn pg.Conn) (err error) { func(conn pg.Conn) (err error) {
memberships := coredata.Memberships{} memberships := coredata.Memberships{}
count, err = memberships.CountByUserID(ctx, conn, identityID) count, err = memberships.CountByIdentityID(ctx, conn, identityID)
if err != nil { if err != nil {
return fmt.Errorf("cannot count memberships: %w", err) return fmt.Errorf("cannot count memberships: %w", err)
} }
@@ -407,17 +407,17 @@ func (s AccountService) ChangePassword(ctx context.Context, identityID gid.GID,
return s.pg.WithTx( return s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
user := &coredata.User{} identity := &coredata.Identity{}
err := user.LoadByID(ctx, tx, identityID) err := identity.LoadByID(ctx, tx, identityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(identityID) return NewIdentityNotFoundError(identityID)
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
isLegacyPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.CurrentPassword), user.HashedPassword) isLegacyPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(req.CurrentPassword), identity.HashedPassword)
if err != nil { if err != nil {
return fmt.Errorf("cannot compare legacy password: %w", err) return fmt.Errorf("cannot compare legacy password: %w", err)
} }
@@ -431,15 +431,15 @@ func (s AccountService) ChangePassword(ctx context.Context, identityID gid.GID,
return fmt.Errorf("cannot hash new password: %w", err) return fmt.Errorf("cannot hash new password: %w", err)
} }
user.HashedPassword = newPasswordHash identity.HashedPassword = newPasswordHash
user.UpdatedAt = time.Now() identity.UpdatedAt = time.Now()
err = user.Update(ctx, tx) err = identity.Update(ctx, tx)
if err != nil { if err != nil {
return fmt.Errorf("cannot update user: %w", err) return fmt.Errorf("cannot update identity: %w", err)
} }
// TODO: email to notify user that their password has been changed // TODO: email to notify identity that their password has been changed
return nil return nil
}, },
@@ -453,7 +453,7 @@ func (s AccountService) CountSessions(ctx context.Context, identityID gid.GID) (
ctx, ctx,
func(conn pg.Conn) (err error) { func(conn pg.Conn) (err error) {
sessions := coredata.Sessions{} sessions := coredata.Sessions{}
count, err = sessions.CountByUserID(ctx, conn, identityID) count, err = sessions.CountByIdentityID(ctx, conn, identityID)
if err != nil { if err != nil {
return fmt.Errorf("cannot count sessions: %w", err) return fmt.Errorf("cannot count sessions: %w", err)
} }
@@ -475,7 +475,7 @@ func (s AccountService) ListSessions(
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := sessions.LoadByUserID(ctx, conn, identityID, cursor) err := sessions.LoadByIdentityID(ctx, conn, identityID, cursor)
if err != nil { if err != nil {
return fmt.Errorf("cannot load sessions: %w", err) return fmt.Errorf("cannot load sessions: %w", err)
} }
@@ -491,19 +491,19 @@ func (s AccountService) ListSessions(
return page.NewPage(sessions, cursor), nil return page.NewPage(sessions, cursor), nil
} }
func (s AccountService) GetIdentity(ctx context.Context, identityID gid.GID) (*coredata.User, error) { func (s AccountService) GetIdentity(ctx context.Context, identityID gid.GID) (*coredata.Identity, error) {
user := &coredata.User{} identity := &coredata.Identity{}
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := user.LoadByID(ctx, conn, identityID) err := identity.LoadByID(ctx, conn, identityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(identityID) return NewIdentityNotFoundError(identityID)
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
return nil return nil
@@ -513,20 +513,20 @@ func (s AccountService) GetIdentity(ctx context.Context, identityID gid.GID) (*c
return nil, err return nil, err
} }
return user, nil return identity, nil
} }
func (s AccountService) ListPersonalAPIKeys( func (s AccountService) ListPersonalAPIKeys(
ctx context.Context, ctx context.Context,
identityID gid.GID, identityID gid.GID,
cursor *page.Cursor[coredata.UserAPIKeyOrderField], cursor *page.Cursor[coredata.PersonalAPIKeyOrderField],
) (*page.Page[*coredata.UserAPIKey, coredata.UserAPIKeyOrderField], error) { ) (*page.Page[*coredata.PersonalAPIKey, coredata.PersonalAPIKeyOrderField], error) {
var personalAccessTokens coredata.UserAPIKeys var personalAccessTokens coredata.PersonalAPIKeys
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := personalAccessTokens.LoadByUserID(ctx, conn, identityID) err := personalAccessTokens.LoadByIdentityID(ctx, conn, identityID)
if err != nil { if err != nil {
return fmt.Errorf("cannot load personal access tokens: %w", err) return fmt.Errorf("cannot load personal access tokens: %w", err)
} }
@@ -548,8 +548,8 @@ func (s AccountService) CountPersonalAPIKeys(ctx context.Context, identityID gid
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) (err error) { func(conn pg.Conn) (err error) {
personalAccessTokens := coredata.UserAPIKeys{} personalAccessTokens := coredata.PersonalAPIKeys{}
count, err = personalAccessTokens.CountByUserID(ctx, conn, identityID) count, err = personalAccessTokens.CountByIdentityID(ctx, conn, identityID)
if err != nil { if err != nil {
return fmt.Errorf("cannot count personal access tokens: %w", err) return fmt.Errorf("cannot count personal access tokens: %w", err)
} }
@@ -561,10 +561,10 @@ func (s AccountService) CountPersonalAPIKeys(ctx context.Context, identityID gid
return count, err return count, err
} }
func (s AccountService) GetIdentityForMembership(ctx context.Context, membershipID gid.GID) (*coredata.User, error) { func (s AccountService) GetIdentityForMembership(ctx context.Context, membershipID gid.GID) (*coredata.Identity, error) {
var ( var (
scope = coredata.NewScopeFromObjectID(membershipID) scope = coredata.NewScopeFromObjectID(membershipID)
identity = &coredata.User{} identity = &coredata.Identity{}
) )
err := s.pg.WithConn( err := s.pg.WithConn(
@@ -580,10 +580,10 @@ func (s AccountService) GetIdentityForMembership(ctx context.Context, membership
return fmt.Errorf("cannot load membership: %w", err) return fmt.Errorf("cannot load membership: %w", err)
} }
err = identity.LoadByID(ctx, conn, membership.UserID) err = identity.LoadByID(ctx, conn, membership.IdentityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(membership.UserID) return NewIdentityNotFoundError(membership.IdentityID)
} }
return fmt.Errorf("cannot load identity: %w", err) return fmt.Errorf("cannot load identity: %w", err)
@@ -605,10 +605,10 @@ func (s *AccountService) CreatePersonalAPIKey(
identityID gid.GID, identityID gid.GID,
name string, name string,
expiresAt time.Time, expiresAt time.Time,
) (*coredata.UserAPIKey, string, error) { ) (*coredata.PersonalAPIKey, string, error) {
var ( var (
userAPIKey *coredata.UserAPIKey personalAPIKey *coredata.PersonalAPIKey
token string token string
) )
err := s.pg.WithTx( err := s.pg.WithTx(
@@ -616,33 +616,33 @@ func (s *AccountService) CreatePersonalAPIKey(
func(tx pg.Conn) (err error) { func(tx pg.Conn) (err error) {
now := time.Now() now := time.Now()
userAPIKey = &coredata.UserAPIKey{ personalAPIKey = &coredata.PersonalAPIKey{
ID: gid.New(gid.NilTenant, coredata.UserAPIKeyEntityType), ID: gid.New(gid.NilTenant, coredata.PersonalAPIKeyEntityType),
UserID: identityID, IdentityID: identityID,
Name: name, Name: name,
ExpiresAt: expiresAt, ExpiresAt: expiresAt,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
if err := userAPIKey.Insert(ctx, tx); err != nil { if err := personalAPIKey.Insert(ctx, tx); err != nil {
return fmt.Errorf("cannot insert user api key: %w", err) return fmt.Errorf("cannot insert personal api key: %w", err)
} }
token, err = statelesstoken.NewDeterministicToken( token, err = statelesstoken.NewDeterministicToken(
s.tokenSecret, s.tokenSecret,
TokenTypeAPIKey, TokenTypeAPIKey,
userAPIKey.ExpiresAt, personalAPIKey.ExpiresAt,
userAPIKey.CreatedAt, personalAPIKey.CreatedAt,
UserAPIKeyTokenData{ PersonalAPIKeyTokenData{
Version: 2, Version: 2,
KeyID: userAPIKey.ID, KeyID: personalAPIKey.ID,
PrincipalID: identityID, PrincipalID: identityID,
IssuedAt: userAPIKey.CreatedAt, IssuedAt: personalAPIKey.CreatedAt,
}, },
) )
if err != nil { if err != nil {
return fmt.Errorf("cannot generate user api key token: %w", err) return fmt.Errorf("cannot generate personal api key token: %w", err)
} }
return nil return nil
@@ -653,34 +653,34 @@ func (s *AccountService) CreatePersonalAPIKey(
return nil, "", err return nil, "", err
} }
return userAPIKey, token, nil return personalAPIKey, token, nil
} }
func (s *AccountService) DeletePersonalAPIKey( func (s *AccountService) DeletePersonalAPIKey(
ctx context.Context, ctx context.Context,
identityID gid.GID, identityID gid.GID,
userAPIKeyID gid.GID, personalAPIKeyID gid.GID,
) error { ) error {
return s.pg.WithTx( return s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
userAPIKey := &coredata.UserAPIKey{} personalAPIKey := &coredata.PersonalAPIKey{}
err := userAPIKey.LoadByID(ctx, tx, userAPIKeyID) err := personalAPIKey.LoadByID(ctx, tx, personalAPIKeyID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserAPIKeyNotFoundError(userAPIKeyID) return NewPersonalAPIKeyNotFoundError(personalAPIKeyID)
} }
return fmt.Errorf("cannot load user api key: %w", err) return fmt.Errorf("cannot load personal api key: %w", err)
} }
if userAPIKey.UserID != identityID { if personalAPIKey.IdentityID != identityID {
return NewUserAPIKeyNotFoundError(userAPIKeyID) return NewPersonalAPIKeyNotFoundError(personalAPIKeyID)
} }
err = userAPIKey.Delete(ctx, tx) err = personalAPIKey.Delete(ctx, tx)
if err != nil { if err != nil {
return fmt.Errorf("cannot delete user api key: %w", err) return fmt.Errorf("cannot delete personal api key: %w", err)
} }
return nil return nil
@@ -699,7 +699,7 @@ func (s AccountService) ListOrganizations(ctx context.Context, identityID gid.GI
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := organizations.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor) err := organizations.LoadByIdentityID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
if err != nil { if err != nil {
return fmt.Errorf("cannot load organizations: %w", err) return fmt.Errorf("cannot load organizations: %w", err)
} }
@@ -714,35 +714,3 @@ func (s AccountService) ListOrganizations(ctx context.Context, identityID gid.GI
return organizations, nil return organizations, nil
} }
// func (s AccountService) AllAccessibleTenants(ctx context.Context, identityID gid.GID) ([]gid.TenantID, error) {
// var tenants []gid.TenantID
// err := s.pg.WithConn(
// ctx,
// func(conn pg.Conn) error {
// memberships := coredata.Memberships{}
// orderBy := page.OrderBy[coredata.MembershipOrderField]{
// Field: coredata.MembershipOrderFieldCreatedAt,
// Direction: page.OrderDirectionDesc,
// }
// cursor := page.NewCursor(1000, nil, page.Head, orderBy)
// err := memberships.LoadByUserID(ctx, conn, coredata.NewNoScope(), identityID, cursor)
// if err != nil {
// return fmt.Errorf("cannot load memberships: %w", err)
// }
// for _, membership := range memberships {
// tenants = append(tenants, membership.ID.TenantID())
// }
// return nil
// },
// )
// if err != nil {
// return nil, err
// }
// return tenants, nil
// }

View File

@@ -35,9 +35,9 @@ func NewAPIKeyService(svc *Service) *APIKeyService {
return &APIKeyService{Service: svc} return &APIKeyService{Service: svc}
} }
func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata.UserAPIKey, error) { func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata.PersonalAPIKey, error) {
var ( var (
apiKey = &coredata.UserAPIKey{} apiKey = &coredata.PersonalAPIKey{}
now = time.Now() now = time.Now()
) )
@@ -46,12 +46,12 @@ func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata
func(tx pg.Conn) error { func(tx pg.Conn) error {
if err := apiKey.LoadByID(ctx, tx, keyID); err != nil { if err := apiKey.LoadByID(ctx, tx, keyID); err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserAPIKeyNotFoundError(keyID) return NewPersonalAPIKeyNotFoundError(keyID)
} }
} }
if apiKey.ExpireReason != nil { if apiKey.ExpireReason != nil {
return NewUserAPIKeyExpiredError(keyID) return NewPersonalAPIKeyExpiredError(keyID)
} }
if now.After(apiKey.ExpiresAt) { if now.After(apiKey.ExpiresAt) {
@@ -60,10 +60,10 @@ func (s *APIKeyService) GetAPIKey(ctx context.Context, keyID gid.GID) (*coredata
apiKey.UpdatedAt = now apiKey.UpdatedAt = now
if err := apiKey.Update(ctx, tx); err != nil { if err := apiKey.Update(ctx, tx); err != nil {
return fmt.Errorf("cannot update user api key: %w", err) return fmt.Errorf("cannot update personal api key: %w", err)
} }
return NewUserAPIKeyExpiredError(keyID) return NewPersonalAPIKeyExpiredError(keyID)
} }
return nil return nil

View File

@@ -110,7 +110,7 @@ func (req CreateIdentityWithPasswordRequest) Validate() error {
func (s *AuthService) CreateIdentityFromInvitation( func (s *AuthService) CreateIdentityFromInvitation(
ctx context.Context, ctx context.Context,
req *CreateIdentityFromInvitationRequest, req *CreateIdentityFromInvitationRequest,
) (*coredata.User, *coredata.Session, error) { ) (*coredata.Identity, *coredata.Session, error) {
if err := req.Validate(); err != nil { if err := req.Validate(); err != nil {
return nil, nil, fmt.Errorf("invalid request: %w", err) return nil, nil, fmt.Errorf("invalid request: %w", err)
} }
@@ -123,7 +123,7 @@ func (s *AuthService) CreateIdentityFromInvitation(
var ( var (
scope = coredata.NewScopeFromObjectID(payload.Data.InvitationID) scope = coredata.NewScopeFromObjectID(payload.Data.InvitationID)
invitation = &coredata.Invitation{} invitation = &coredata.Invitation{}
user = &coredata.User{} identity = &coredata.Identity{}
session = &coredata.Session{} session = &coredata.Session{}
now = time.Now() now = time.Now()
) )
@@ -153,8 +153,8 @@ func (s *AuthService) CreateIdentityFromInvitation(
return NewInvitationExpiredError(payload.Data.InvitationID) return NewInvitationExpiredError(payload.Data.InvitationID)
} }
user = &coredata.User{ identity = &coredata.Identity{
ID: gid.New(gid.NilTenant, coredata.UserEntityType), ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
EmailAddress: invitation.Email, EmailAddress: invitation.Email,
HashedPassword: hashedPassword, HashedPassword: hashedPassword,
EmailAddressVerified: true, EmailAddressVerified: true,
@@ -163,16 +163,16 @@ func (s *AuthService) CreateIdentityFromInvitation(
UpdatedAt: now, UpdatedAt: now,
} }
err = user.Insert(ctx, tx) err = identity.Insert(ctx, tx)
if err != nil { if err != nil {
if err == coredata.ErrResourceAlreadyExists { if err == coredata.ErrResourceAlreadyExists {
return NewUserAlreadyExistsError(invitation.Email) return NewIdentityAlreadyExistsError(invitation.Email)
} }
return fmt.Errorf("cannot insert user: %w", err) return fmt.Errorf("cannot insert identity: %w", err)
} }
session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, s.sessionDuration) session = coredata.NewRootSession(identity.ID, coredata.AuthMethodPassword, s.sessionDuration)
err = session.Insert(ctx, tx) err = session.Insert(ctx, tx)
if err != nil { if err != nil {
return fmt.Errorf("cannot insert session: %w", err) return fmt.Errorf("cannot insert session: %w", err)
@@ -186,7 +186,7 @@ func (s *AuthService) CreateIdentityFromInvitation(
return nil, nil, err return nil, nil, err
} }
return user, session, nil return identity, session, nil
} }
func (s AuthService) ResetPassword( func (s AuthService) ResetPassword(
@@ -210,26 +210,26 @@ func (s AuthService) ResetPassword(
return s.pg.WithTx( return s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
user := &coredata.User{} identity := &coredata.Identity{}
err := user.LoadByEmail(ctx, tx, payload.Data.Email) err := identity.LoadByEmail(ctx, tx, payload.Data.Email)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return nil // Don't leak information about non-existent users return nil // Don't leak information about non-existent identities
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
user.HashedPassword = hashedPassword identity.HashedPassword = hashedPassword
user.UpdatedAt = time.Now() identity.UpdatedAt = time.Now()
err = user.Update(ctx, tx) err = identity.Update(ctx, tx)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return nil // Don't leak information about non-existent users return nil // Don't leak information about non-existent identities
} }
return fmt.Errorf("cannot update user: %w", err) return fmt.Errorf("cannot update identity: %w", err)
} }
return nil return nil
@@ -264,18 +264,18 @@ func (s AuthService) SendPasswordResetInstructionByEmail(
return s.pg.WithTx( return s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
user := &coredata.User{} identity := &coredata.Identity{}
if err := user.LoadByEmail(ctx, tx, email); err != nil { if err := identity.LoadByEmail(ctx, tx, email); err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return nil // Don't leak information about non-existent users return nil // Don't leak information about non-existent identities
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
subject, textBody, htmlBody, err := emails.RenderPasswordReset( subject, textBody, htmlBody, err := emails.RenderPasswordReset(
s.baseURL, s.baseURL,
user.FullName, identity.FullName,
resetPasswordUrl, resetPasswordUrl,
) )
if err != nil { if err != nil {
@@ -283,8 +283,8 @@ func (s AuthService) SendPasswordResetInstructionByEmail(
} }
passwordResetEmail := coredata.NewEmail( passwordResetEmail := coredata.NewEmail(
user.FullName, identity.FullName,
user.EmailAddress, identity.EmailAddress,
subject, subject,
textBody, textBody,
htmlBody, htmlBody,
@@ -303,7 +303,7 @@ func (s AuthService) SendPasswordResetInstructionByEmail(
func (s AuthService) CreateIdentityWithPassword( func (s AuthService) CreateIdentityWithPassword(
ctx context.Context, ctx context.Context,
req *CreateIdentityWithPasswordRequest, req *CreateIdentityWithPasswordRequest,
) (*coredata.User, *coredata.Session, error) { ) (*coredata.Identity, *coredata.Session, error) {
if s.disableSignup { // TODO Rename this one to disableSignup if s.disableSignup { // TODO Rename this one to disableSignup
return nil, nil, NewErrSignupDisabled() return nil, nil, NewErrSignupDisabled()
} }
@@ -320,8 +320,8 @@ func (s AuthService) CreateIdentityWithPassword(
var ( var (
now = time.Now() now = time.Now()
user = &coredata.User{ identity = &coredata.Identity{
ID: gid.New(gid.NilTenant, coredata.UserEntityType), ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
EmailAddress: req.Email, EmailAddress: req.Email,
HashedPassword: hashedPassword, HashedPassword: hashedPassword,
EmailAddressVerified: false, EmailAddressVerified: false,
@@ -330,14 +330,14 @@ func (s AuthService) CreateIdentityWithPassword(
UpdatedAt: now, UpdatedAt: now,
} }
session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, 24*time.Hour*7) session = coredata.NewRootSession(identity.ID, coredata.AuthMethodPassword, 24*time.Hour*7)
) )
confirmationToken, err := statelesstoken.NewToken( confirmationToken, err := statelesstoken.NewToken(
s.tokenSecret, s.tokenSecret,
TokenTypeEmailConfirmation, TokenTypeEmailConfirmation,
24*time.Hour, 24*time.Hour,
EmailConfirmationData{UserID: user.ID, Email: user.EmailAddress}, EmailConfirmationData{IdentityID: identity.ID, Email: identity.EmailAddress},
) )
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("cannot generate confirmation token: %w", err) return nil, nil, fmt.Errorf("cannot generate confirmation token: %w", err)
@@ -358,7 +358,7 @@ func (s AuthService) CreateIdentityWithPassword(
subject, textBody, htmlBody, err := emails.RenderConfirmEmail( subject, textBody, htmlBody, err := emails.RenderConfirmEmail(
s.baseURL, s.baseURL,
user.FullName, identity.FullName,
confirmationUrl, confirmationUrl,
) )
if err != nil { if err != nil {
@@ -366,8 +366,8 @@ func (s AuthService) CreateIdentityWithPassword(
} }
confirmationEmail := coredata.NewEmail( confirmationEmail := coredata.NewEmail(
user.FullName, identity.FullName,
user.EmailAddress, identity.EmailAddress,
subject, subject,
textBody, textBody,
htmlBody, htmlBody,
@@ -376,13 +376,13 @@ func (s AuthService) CreateIdentityWithPassword(
err = s.pg.WithTx( err = s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
err := user.Insert(ctx, tx) err := identity.Insert(ctx, tx)
if err != nil { if err != nil {
if err == coredata.ErrResourceAlreadyExists { if err == coredata.ErrResourceAlreadyExists {
return NewUserAlreadyExistsError(user.EmailAddress) return NewIdentityAlreadyExistsError(identity.EmailAddress)
} }
return fmt.Errorf("cannot insert user: %w", err) return fmt.Errorf("cannot insert identity: %w", err)
} }
if err := confirmationEmail.Insert(ctx, tx); err != nil { if err := confirmationEmail.Insert(ctx, tx); err != nil {
@@ -397,16 +397,16 @@ func (s AuthService) CreateIdentityWithPassword(
}, },
) )
return user, session, err return identity, session, err
} }
func (s AuthService) OpenSessionWithSAML(ctx context.Context, userID gid.GID, organizationID gid.GID) (*coredata.Session, error) { func (s AuthService) OpenSessionWithSAML(ctx context.Context, identityID gid.GID, organizationID gid.GID) (*coredata.Session, error) {
session := &coredata.Session{} session := &coredata.Session{}
err := s.pg.WithTx( err := s.pg.WithTx(
ctx, ctx,
func(conn pg.Conn) (err error) { func(conn pg.Conn) (err error) {
session = coredata.NewRootSession(userID, coredata.AuthMethodSAML, s.sessionDuration) session = coredata.NewRootSession(identityID, coredata.AuthMethodSAML, s.sessionDuration)
err = session.Insert(ctx, conn) err = session.Insert(ctx, conn)
if err != nil { if err != nil {
return fmt.Errorf("cannot insert session: %w", err) return fmt.Errorf("cannot insert session: %w", err)
@@ -423,7 +423,7 @@ func (s AuthService) OpenSessionWithSAML(ctx context.Context, userID gid.GID, or
return session, nil return session, nil
} }
func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Addr, password string) (*coredata.User, *coredata.Session, error) { func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Addr, password string) (*coredata.Identity, *coredata.Session, error) {
v := validator.New() v := validator.New()
v.Check(password, "password", PasswordValidator()) v.Check(password, "password", PasswordValidator())
@@ -433,29 +433,29 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add
} }
var ( var (
user = &coredata.User{} identity = &coredata.Identity{}
session = &coredata.Session{} session = &coredata.Session{}
) )
err = s.pg.WithTx( err = s.pg.WithTx(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := user.LoadByEmail(ctx, conn, email) err := identity.LoadByEmail(ctx, conn, email)
if err != nil { if err != nil {
// Do not leak information about non-existent users // Do not leak information about non-existent identities
if err != coredata.ErrResourceNotFound { if err != coredata.ErrResourceNotFound {
return fmt.Errorf("cannot load user by email: %w", err) return fmt.Errorf("cannot load identity by email: %w", err)
} }
} }
// Perform a password comparison even when the user does not exist to mitigate timing attacks // Perform a password comparison even when the identity does not exist to mitigate timing attacks
// and prevent revealing account existence. // and prevent revealing account existence.
if user.ID == gid.Nil { if identity.ID == gid.Nil {
s.hp.ComparePasswordAndHash([]byte(password+"qwertyuiop1234567890"), []byte("qwertyuiop1234567890")) s.hp.ComparePasswordAndHash([]byte(password+"qwertyuiop1234567890"), []byte("qwertyuiop1234567890"))
return NewInvalidCredentialsError("invalid email or password") return NewInvalidCredentialsError("invalid email or password")
} }
isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(password), user.HashedPassword) isPasswordMatch, err := s.hp.ComparePasswordAndHash([]byte(password), identity.HashedPassword)
if err != nil { if err != nil {
return fmt.Errorf("cannot verify password: %w", err) return fmt.Errorf("cannot verify password: %w", err)
} }
@@ -464,7 +464,7 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add
return NewInvalidCredentialsError("invalid email or password") return NewInvalidCredentialsError("invalid email or password")
} }
session = coredata.NewRootSession(user.ID, coredata.AuthMethodPassword, s.sessionDuration) session = coredata.NewRootSession(identity.ID, coredata.AuthMethodPassword, s.sessionDuration)
err = session.Insert(ctx, conn) err = session.Insert(ctx, conn)
if err != nil { if err != nil {
return fmt.Errorf("cannot insert session: %w", err) return fmt.Errorf("cannot insert session: %w", err)
@@ -474,5 +474,5 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add
}, },
) )
return user, session, err return identity, session, err
} }

View File

@@ -70,7 +70,7 @@ type AuthorizeParams struct {
// It combines self-management policies with role-based policies. // It combines self-management policies with role-based policies.
func (a *Authorizer) Authorize(ctx context.Context, params AuthorizeParams) error { func (a *Authorizer) Authorize(ctx context.Context, params AuthorizeParams) error {
// Validate principal type // Validate principal type
if params.Principal.EntityType() != coredata.UserEntityType { if params.Principal.EntityType() != coredata.IdentityEntityType {
return NewUnsupportedPrincipalTypeError(params.Principal.EntityType()) return NewUnsupportedPrincipalTypeError(params.Principal.EntityType())
} }
@@ -135,7 +135,7 @@ func (a *Authorizer) loadRolePolicies(ctx context.Context, principalID gid.GID,
scope := coredata.NewScope(resourceID.TenantID()) scope := coredata.NewScope(resourceID.TenantID())
var m coredata.Membership var m coredata.Membership
if err := m.LoadRoleByUserAndEntityID(ctx, conn, scope, principalID, resourceID); err != nil { if err := m.LoadRoleByIdentityAndEntityID(ctx, conn, scope, principalID, resourceID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) { if errors.Is(err, coredata.ErrResourceNotFound) {
return nil // No membership = no role-based policies return nil // No membership = no role-based policies
} }

View File

@@ -61,14 +61,14 @@ func (e ErrInvitationExpired) Error() string {
return fmt.Sprintf("invitation %q expired", e.InvitationID) return fmt.Sprintf("invitation %q expired", e.InvitationID)
} }
type ErrUserAlreadyExists struct{ EmailAddress mail.Addr } type ErrIdentityAlreadyExists struct{ EmailAddress mail.Addr }
func NewUserAlreadyExistsError(emailAddress mail.Addr) error { func NewIdentityAlreadyExistsError(emailAddress mail.Addr) error {
return &ErrUserAlreadyExists{EmailAddress: emailAddress} return &ErrIdentityAlreadyExists{EmailAddress: emailAddress}
} }
func (e ErrUserAlreadyExists) Error() string { func (e ErrIdentityAlreadyExists) Error() string {
return fmt.Sprintf("user %q already exists", e.EmailAddress.String()) return fmt.Sprintf("identity %q already exists", e.EmailAddress.String())
} }
type ErrEmailAlreadyVerified struct{ message string } type ErrEmailAlreadyVerified struct{ message string }
@@ -81,14 +81,14 @@ func (e ErrEmailAlreadyVerified) Error() string {
return e.message return e.message
} }
type ErrUserNotFound struct{ UserID gid.GID } type ErrIdentityNotFound struct{ IdentityID gid.GID }
func NewUserNotFoundError(userID gid.GID) error { func NewIdentityNotFoundError(identityID gid.GID) error {
return &ErrUserNotFound{userID} return &ErrIdentityNotFound{identityID}
} }
func (e ErrUserNotFound) Error() string { func (e ErrIdentityNotFound) Error() string {
return fmt.Sprintf("user %q not found", e.UserID) return fmt.Sprintf("identity %q not found", e.IdentityID)
} }
type ErrInvalidPassword struct{ message string } type ErrInvalidPassword struct{ message string }
@@ -168,16 +168,16 @@ func (e ErrSessionExpired) Error() string {
} }
type ErrMembershipAlreadyExists struct { type ErrMembershipAlreadyExists struct {
UserID gid.GID IdentityID gid.GID
OrganizationID gid.GID OrganizationID gid.GID
} }
func NewMembershipAlreadyExistsError(userID gid.GID, organizationID gid.GID) error { func NewMembershipAlreadyExistsError(identityID gid.GID, organizationID gid.GID) error {
return &ErrMembershipAlreadyExists{UserID: userID, OrganizationID: organizationID} return &ErrMembershipAlreadyExists{IdentityID: identityID, OrganizationID: organizationID}
} }
func (e ErrMembershipAlreadyExists) Error() string { func (e ErrMembershipAlreadyExists) Error() string {
return fmt.Sprintf("membership already exists for user %q in organization %q", e.UserID, e.OrganizationID) return fmt.Sprintf("membership already exists for identity %q in organization %q", e.IdentityID, e.OrganizationID)
} }
type ErrSAMLConfigurationNotFound struct{ ConfigID gid.GID } type ErrSAMLConfigurationNotFound struct{ ConfigID gid.GID }
@@ -190,24 +190,24 @@ func (e ErrSAMLConfigurationNotFound) Error() string {
return fmt.Sprintf("SAML configuration %q not found", e.ConfigID) return fmt.Sprintf("SAML configuration %q not found", e.ConfigID)
} }
type ErrUserAPIKeyNotFound struct{ UserAPIKeyID gid.GID } type ErrPersonalAPIKeyNotFound struct{ PersonalAPIKeyID gid.GID }
func NewUserAPIKeyNotFoundError(userAPIKeyID gid.GID) error { func NewPersonalAPIKeyNotFoundError(personalAPIKeyID gid.GID) error {
return &ErrUserAPIKeyNotFound{UserAPIKeyID: userAPIKeyID} return &ErrPersonalAPIKeyNotFound{PersonalAPIKeyID: personalAPIKeyID}
} }
func (e ErrUserAPIKeyNotFound) Error() string { func (e ErrPersonalAPIKeyNotFound) Error() string {
return fmt.Sprintf("user API key %q not found", e.UserAPIKeyID) return fmt.Sprintf("personal API key %q not found", e.PersonalAPIKeyID)
} }
type ErrUserAPIKeyExpired struct{ UserAPIKeyID gid.GID } type ErrPersonalAPIKeyExpired struct{ PersonalAPIKeyID gid.GID }
func NewUserAPIKeyExpiredError(userAPIKeyID gid.GID) error { func NewPersonalAPIKeyExpiredError(personalAPIKeyID gid.GID) error {
return &ErrUserAPIKeyExpired{UserAPIKeyID: userAPIKeyID} return &ErrPersonalAPIKeyExpired{PersonalAPIKeyID: personalAPIKeyID}
} }
func (e ErrUserAPIKeyExpired) Error() string { func (e ErrPersonalAPIKeyExpired) Error() string {
return fmt.Sprintf("user API key %q expired", e.UserAPIKeyID) return fmt.Sprintf("personal API key %q expired", e.PersonalAPIKeyID)
} }
type ErrSAMLConfigurationDomainNotVerified struct{ ConfigID gid.GID } type ErrSAMLConfigurationDomainNotVerified struct{ ConfigID gid.GID }

View File

@@ -307,22 +307,22 @@ func (s *OrganizationService) InviteMember(
return fmt.Errorf("cannot load organization: %w", err) return fmt.Errorf("cannot load organization: %w", err)
} }
user := &coredata.User{} identity := &coredata.Identity{}
err = user.LoadByEmail(ctx, tx, emailAddress) err = identity.LoadByEmail(ctx, tx, emailAddress)
if err != nil && err != coredata.ErrResourceNotFound { if err != nil && err != coredata.ErrResourceNotFound {
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
userExists := user.ID != gid.Nil identityExists := identity.ID != gid.Nil
if userExists { if identityExists {
membership := &coredata.Membership{} membership := &coredata.Membership{}
err = membership.LoadByUserAndOrg(ctx, tx, scope, user.ID, organizationID) err = membership.LoadByIdentityAndOrg(ctx, tx, scope, identity.ID, organizationID)
if err != nil && err != coredata.ErrResourceNotFound { if err != nil && err != coredata.ErrResourceNotFound {
return fmt.Errorf("cannot load membership: %w", err) return fmt.Errorf("cannot load membership: %w", err)
} }
if membership.ID != gid.Nil { if membership.ID != gid.Nil {
return NewMembershipAlreadyExistsError(user.ID, organizationID) return NewMembershipAlreadyExistsError(identity.ID, organizationID)
} }
} }
@@ -506,7 +506,7 @@ func (s *OrganizationService) CreateOrganization(
membership := &coredata.Membership{ membership := &coredata.Membership{
ID: gid.New(tenantID, coredata.MembershipEntityType), ID: gid.New(tenantID, coredata.MembershipEntityType),
UserID: identityID, IdentityID: identityID,
OrganizationID: organizationID, OrganizationID: organizationID,
Role: coredata.MembershipRoleOwner, Role: coredata.MembershipRoleOwner,
CreatedAt: now, CreatedAt: now,

View File

@@ -413,7 +413,7 @@ var Permissions = map[uint16]map[Action][]Role{
ActionGetTrustCenterFile: EditRoles, ActionGetTrustCenterFile: EditRoles,
ActionDeleteTrustCenterFile: EditRoles, ActionDeleteTrustCenterFile: EditRoles,
}, },
coredata.UserEntityType: { coredata.IdentityEntityType: {
ActionGet: NonEmployeeRoles, ActionGet: NonEmployeeRoles,
}, },
coredata.MembershipEntityType: { coredata.MembershipEntityType: {

View File

@@ -171,10 +171,10 @@ func (s *Service) HandleAssertion(
ctx context.Context, ctx context.Context,
samlResponse string, samlResponse string,
configID gid.GID, configID gid.GID,
) (*coredata.User, *coredata.Membership, error) { ) (*coredata.Identity, *coredata.Membership, error) {
var ( var (
now = time.Now() now = time.Now()
user = &coredata.User{} identity = &coredata.Identity{}
membership = &coredata.Membership{} membership = &coredata.Membership{}
) )
@@ -251,12 +251,12 @@ func (s *Service) HandleAssertion(
return NewEmailDomainMismatchError(email, config.EmailDomain) return NewEmailDomainMismatchError(email, config.EmailDomain)
} }
err = user.LoadByEmail(ctx, tx, email) err = identity.LoadByEmail(ctx, tx, email)
if err == coredata.ErrResourceNotFound && !config.AutoSignupEnabled { if err == coredata.ErrResourceNotFound && !config.AutoSignupEnabled {
return NewSAMLAutoSignupDisabledError(config.ID) return NewSAMLAutoSignupDisabledError(config.ID)
} else if err == coredata.ErrResourceNotFound && config.AutoSignupEnabled { } else if err == coredata.ErrResourceNotFound && config.AutoSignupEnabled {
*user = coredata.User{ *identity = coredata.Identity{
ID: gid.New(gid.NilTenant, coredata.UserEntityType), ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
EmailAddress: email, EmailAddress: email,
HashedPassword: nil, HashedPassword: nil,
EmailAddressVerified: true, EmailAddressVerified: true,
@@ -265,26 +265,26 @@ func (s *Service) HandleAssertion(
UpdatedAt: now, UpdatedAt: now,
} }
err := user.Insert(ctx, tx) err := identity.Insert(ctx, tx)
if err != nil { if err != nil {
return fmt.Errorf("cannot insert user: %w", err) return fmt.Errorf("cannot insert identity: %w", err)
} }
} else if err != nil { } else if err != nil {
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} else { } else {
user.SAMLSubject = &assertion.Subject.NameID.Value identity.SAMLSubject = &assertion.Subject.NameID.Value
user.FullName = fullname identity.FullName = fullname
user.EmailAddress = email identity.EmailAddress = email
user.EmailAddressVerified = true identity.EmailAddressVerified = true
user.UpdatedAt = now identity.UpdatedAt = now
err = user.Update(ctx, tx) err = identity.Update(ctx, tx)
if err != nil { if err != nil {
return fmt.Errorf("cannot update user: %w", err) return fmt.Errorf("cannot update identity: %w", err)
} }
} }
err = membership.LoadByUserAndOrg(ctx, tx, coredata.NewNoScope(), user.ID, config.OrganizationID) err = membership.LoadByIdentityAndOrg(ctx, tx, coredata.NewNoScope(), identity.ID, config.OrganizationID)
if err != nil && err != coredata.ErrResourceNotFound { if err != nil && err != coredata.ErrResourceNotFound {
return fmt.Errorf("cannot load membership: %w", err) return fmt.Errorf("cannot load membership: %w", err)
} }
@@ -293,7 +293,7 @@ func (s *Service) HandleAssertion(
if !isMember { if !isMember {
membership = &coredata.Membership{ membership = &coredata.Membership{
ID: gid.New(config.ID.TenantID(), coredata.MembershipEntityType), ID: gid.New(config.ID.TenantID(), coredata.MembershipEntityType),
UserID: user.ID, IdentityID: identity.ID,
OrganizationID: config.OrganizationID, OrganizationID: config.OrganizationID,
Role: coredata.MembershipRoleViewer, Role: coredata.MembershipRoleViewer,
CreatedAt: now, CreatedAt: now,
@@ -324,7 +324,7 @@ func (s *Service) HandleAssertion(
return nil, nil, err return nil, nil, err
} }
return user, membership, nil return identity, membership, nil
} }
func (s *Service) validateAssertion(assertion *saml.Assertion, config *coredata.SAMLConfiguration, now time.Time) error { func (s *Service) validateAssertion(assertion *saml.Assertion, config *coredata.SAMLConfiguration, now time.Time) error {

View File

@@ -129,14 +129,14 @@ func (s SessionService) RevokeSession(ctx context.Context, identityID gid.GID, s
return s.pg.WithTx( return s.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
user := &coredata.User{} identity := &coredata.Identity{}
err := user.LoadByID(ctx, tx, identityID) err := identity.LoadByID(ctx, tx, identityID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewUserNotFoundError(identityID) return NewIdentityNotFoundError(identityID)
} }
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
session := &coredata.Session{} session := &coredata.Session{}
@@ -150,7 +150,7 @@ func (s SessionService) RevokeSession(ctx context.Context, identityID gid.GID, s
} }
// TODO: move to dedicated query instead of LoadByID // TODO: move to dedicated query instead of LoadByID
if session.UserID != identityID { if session.IdentityID != identityID {
return NewSessionNotFoundError(sessionID) return NewSessionNotFoundError(sessionID)
} }
@@ -192,7 +192,7 @@ func (s SessionService) RevokeAllSessions(ctx context.Context, currentSessionID
} }
sessions := coredata.Sessions{} sessions := coredata.Sessions{}
count, err = sessions.ExpireAllForUserExceptOneSession(ctx, tx, session.UserID, session.ID) count, err = sessions.ExpireAllForIdentityExceptOneSession(ctx, tx, session.IdentityID, session.ID)
if err != nil { if err != nil {
return fmt.Errorf("cannot expire all sessions: %w", err) return fmt.Errorf("cannot expire all sessions: %w", err)
} }
@@ -323,7 +323,7 @@ func (s SessionService) AssumeOrganizationSession(
var ( var (
now = time.Now() now = time.Now()
rootSession = &coredata.Session{} rootSession = &coredata.Session{}
user = &coredata.User{} identity = &coredata.Identity{}
membership = &coredata.Membership{} membership = &coredata.Membership{}
childSession = &coredata.Session{} childSession = &coredata.Session{}
scope = coredata.NewScopeFromObjectID(organizationID) scope = coredata.NewScopeFromObjectID(organizationID)
@@ -348,12 +348,12 @@ func (s SessionService) AssumeOrganizationSession(
return NewSessionExpiredError(sessionID) return NewSessionExpiredError(sessionID)
} }
err = user.LoadByID(ctx, tx, rootSession.UserID) err = identity.LoadByID(ctx, tx, rootSession.IdentityID)
if err != nil { if err != nil {
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load identity: %w", err)
} }
err = membership.LoadByUserInOrganization(ctx, tx, rootSession.UserID, organizationID) err = membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID)
if err != nil { if err != nil {
if err == coredata.ErrResourceNotFound { if err == coredata.ErrResourceNotFound {
return NewMembershipNotFoundError(organizationID) return NewMembershipNotFoundError(organizationID)
@@ -367,7 +367,7 @@ func (s SessionService) AssumeOrganizationSession(
tx, tx,
scope, scope,
organizationID, organizationID,
user.EmailAddress.Domain(), identity.EmailAddress.Domain(),
) )
if err != nil && err != coredata.ErrResourceNotFound { if err != nil && err != coredata.ErrResourceNotFound {
return fmt.Errorf("cannot load SAML configuration: %w", err) return fmt.Errorf("cannot load SAML configuration: %w", err)
@@ -389,7 +389,7 @@ func (s SessionService) AssumeOrganizationSession(
tenantID := scope.GetTenantID() tenantID := scope.GetTenantID()
childSession = &coredata.Session{ childSession = &coredata.Session{
ID: gid.New(tenantID, coredata.SessionEntityType), ID: gid.New(tenantID, coredata.SessionEntityType),
UserID: rootSession.UserID, IdentityID: rootSession.IdentityID,
TenantID: &tenantID, TenantID: &tenantID,
MembershipID: &membership.ID, MembershipID: &membership.ID,
ParentSessionID: &rootSession.ID, ParentSessionID: &rootSession.ID,

View File

@@ -31,8 +31,8 @@ var (
apiKeyContextKey = &ctxKey{name: "api_key"} apiKeyContextKey = &ctxKey{name: "api_key"}
) )
func APIKeyFromContext(ctx context.Context) *coredata.UserAPIKey { func APIKeyFromContext(ctx context.Context) *coredata.PersonalAPIKey {
apiKey, _ := ctx.Value(apiKeyContextKey).(*coredata.UserAPIKey) apiKey, _ := ctx.Value(apiKeyContextKey).(*coredata.PersonalAPIKey)
return apiKey return apiKey
} }
@@ -62,30 +62,30 @@ func NewAPIKeyMiddleware(svc *iam.Service) func(next http.Handler) http.Handler
apiKey, err := svc.APIKeyService.GetAPIKey(ctx, keyID) apiKey, err := svc.APIKeyService.GetAPIKey(ctx, keyID)
if err != nil { if err != nil {
var errUserAPIKeyNotFound *iam.ErrUserAPIKeyNotFound var errPersonalAPIKeyNotFound *iam.ErrPersonalAPIKeyNotFound
var errUserAPIKeyExpired *iam.ErrUserAPIKeyExpired var errPersonalAPIKeyExpired *iam.ErrPersonalAPIKeyExpired
if errors.As(err, &errUserAPIKeyNotFound) || errors.As(err, &errUserAPIKeyExpired) { if errors.As(err, &errPersonalAPIKeyNotFound) || errors.As(err, &errPersonalAPIKeyExpired) {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
panic(fmt.Errorf("cannot get user API key: %w", err)) panic(fmt.Errorf("cannot get personal API key: %w", err))
} }
user, err := svc.AccountService.GetIdentity(ctx, apiKey.UserID) identity, err := svc.AccountService.GetIdentity(ctx, apiKey.IdentityID)
if err != nil { if err != nil {
var errUserNotFound *iam.ErrUserNotFound var errIdentityNotFound *iam.ErrIdentityNotFound
if errors.As(err, &errUserNotFound) { if errors.As(err, &errIdentityNotFound) {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
panic(fmt.Errorf("cannot get user: %w", err)) panic(fmt.Errorf("cannot get identity: %w", err))
} }
ctx = context.WithValue(ctx, apiKeyContextKey, apiKey) ctx = context.WithValue(ctx, apiKeyContextKey, apiKey)
ctx = context.WithValue(ctx, identityContextKey, user) ctx = context.WithValue(ctx, identityContextKey, identity)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}, },

View File

@@ -70,7 +70,7 @@ func SessionDirective(ctx context.Context, obj any, next graphql.Resolver, requi
} }
func IsViewerDirective(ctx context.Context, obj any, next graphql.Resolver) (any, error) { func IsViewerDirective(ctx context.Context, obj any, next graphql.Resolver) (any, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
switch node := obj.(type) { switch node := obj.(type) {
case *types.Identity: case *types.Identity:

View File

@@ -38,9 +38,9 @@ func SessionFromContext(ctx context.Context) *coredata.Session {
return session return session
} }
func UserFromContext(ctx context.Context) *coredata.User { func IdentityFromContext(ctx context.Context) *coredata.Identity {
user, _ := ctx.Value(identityContextKey).(*coredata.User) identity, _ := ctx.Value(identityContextKey).(*coredata.Identity)
return user return identity
} }
func NewSessionMiddleware(svc *iam.Service, cookieConfig securecookie.Config) func(next http.Handler) http.Handler { func NewSessionMiddleware(svc *iam.Service, cookieConfig securecookie.Config) func(next http.Handler) http.Handler {
@@ -82,16 +82,16 @@ func NewSessionMiddleware(svc *iam.Service, cookieConfig securecookie.Config) fu
panic(fmt.Errorf("cannot get session: %w", err)) panic(fmt.Errorf("cannot get session: %w", err))
} }
user, err := svc.AccountService.GetIdentity(ctx, session.UserID) identity, err := svc.AccountService.GetIdentity(ctx, session.IdentityID)
if err != nil { if err != nil {
var errUserNotFound *iam.ErrUserNotFound var errIdentityNotFound *iam.ErrIdentityNotFound
if errors.As(err, &errUserNotFound) { if errors.As(err, &errIdentityNotFound) {
securecookie.Clear(w, cookieConfig) securecookie.Clear(w, cookieConfig)
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
panic(fmt.Errorf("cannot get user: %w", err)) panic(fmt.Errorf("cannot get identity: %w", err))
} }
userAgent := r.UserAgent() userAgent := r.UserAgent()
@@ -109,7 +109,7 @@ func NewSessionMiddleware(svc *iam.Service, cookieConfig securecookie.Config) fu
} }
ctx = context.WithValue(ctx, sessionContextKey, session) ctx = context.WithValue(ctx, sessionContextKey, session)
ctx = context.WithValue(ctx, identityContextKey, user) ctx = context.WithValue(ctx, identityContextKey, identity)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))

View File

@@ -16,7 +16,7 @@ package types
import "go.probo.inc/probo/pkg/coredata" import "go.probo.inc/probo/pkg/coredata"
func NewIdentity(identity *coredata.User) *Identity { func NewIdentity(identity *coredata.Identity) *Identity {
return &Identity{ return &Identity{
ID: identity.ID, ID: identity.ID,
Email: identity.EmailAddress, Email: identity.EmailAddress,

View File

@@ -62,7 +62,7 @@ func NewMembershipEdge(membership *coredata.Membership, orderField coredata.Memb
func NewMembership(membership *coredata.Membership) *Membership { func NewMembership(membership *coredata.Membership) *Membership {
return &Membership{ return &Membership{
ID: membership.ID, ID: membership.ID,
IdentityID: membership.UserID, IdentityID: membership.IdentityID,
CreatedAt: membership.CreatedAt, CreatedAt: membership.CreatedAt,
// Permissions: membership.Permissions, // Permissions: membership.Permissions,
// ProvisionedBy: membership.ProvisionedBy, // ProvisionedBy: membership.ProvisionedBy,

View File

@@ -21,7 +21,7 @@ import (
) )
type ( type (
PersonalAPIKeyOrderBy OrderBy[coredata.UserAPIKeyOrderField] PersonalAPIKeyOrderBy OrderBy[coredata.PersonalAPIKeyOrderField]
PersonalAPIKeyConnection struct { PersonalAPIKeyConnection struct {
TotalCount int TotalCount int
@@ -34,7 +34,7 @@ type (
) )
func NewPersonalAPIKeyConnection( func NewPersonalAPIKeyConnection(
p *page.Page[*coredata.UserAPIKey, coredata.UserAPIKeyOrderField], p *page.Page[*coredata.PersonalAPIKey, coredata.PersonalAPIKeyOrderField],
resolver any, resolver any,
parentID gid.GID, parentID gid.GID,
) *PersonalAPIKeyConnection { ) *PersonalAPIKeyConnection {
@@ -52,14 +52,14 @@ func NewPersonalAPIKeyConnection(
} }
} }
func NewPersonalAPIKeyEdge(personalAPIKey *coredata.UserAPIKey, orderField coredata.UserAPIKeyOrderField) *PersonalAPIKeyEdge { func NewPersonalAPIKeyEdge(personalAPIKey *coredata.PersonalAPIKey, orderField coredata.PersonalAPIKeyOrderField) *PersonalAPIKeyEdge {
return &PersonalAPIKeyEdge{ return &PersonalAPIKeyEdge{
Node: NewPersonalAPIKey(personalAPIKey), Node: NewPersonalAPIKey(personalAPIKey),
Cursor: personalAPIKey.CursorKey(orderField), Cursor: personalAPIKey.CursorKey(orderField),
} }
} }
func NewPersonalAPIKey(personalAPIKey *coredata.UserAPIKey) *PersonalAPIKey { func NewPersonalAPIKey(personalAPIKey *coredata.PersonalAPIKey) *PersonalAPIKey {
return &PersonalAPIKey{ return &PersonalAPIKey{
ID: personalAPIKey.ID, ID: personalAPIKey.ID,
Name: personalAPIKey.Name, Name: personalAPIKey.Name,

View File

@@ -63,7 +63,7 @@ func NewSession(session *coredata.Session) *Session {
return &Session{ return &Session{
ID: session.ID, ID: session.ID,
IPAddress: session.IPAddress.String(), IPAddress: session.IPAddress.String(),
IdentityID: session.UserID, IdentityID: session.IdentityID,
UserAgent: session.UserAgent, UserAgent: session.UserAgent,
UpdatedAt: session.UpdatedAt, UpdatedAt: session.UpdatedAt,
CreatedAt: session.CreatedAt, CreatedAt: session.CreatedAt,

View File

@@ -89,8 +89,8 @@ func (r *identityResolver) Sessions(ctx context.Context, obj *types.Identity, fi
// PersonalAPIKeys is the resolver for the personalAPIKeys field. // PersonalAPIKeys is the resolver for the personalAPIKeys field.
func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error) { func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error) {
pageOrderBy := page.OrderBy[coredata.UserAPIKeyOrderField]{ pageOrderBy := page.OrderBy[coredata.PersonalAPIKeyOrderField]{
Field: coredata.UserAPIKeyOrderFieldCreatedAt, Field: coredata.PersonalAPIKeyOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc, Direction: page.OrderDirectionDesc,
} }
@@ -233,8 +233,8 @@ func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput)
}, },
) )
if err != nil { if err != nil {
var errUserAlreadyExists *iam.ErrUserAlreadyExists var errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists
if errors.As(err, &errUserAlreadyExists) { if errors.As(err, &errIdentityAlreadyExists) {
return nil, gqlutils.Invalid(err, nil) return nil, gqlutils.Invalid(err, nil)
} }
@@ -287,7 +287,7 @@ func (r *mutationResolver) SignUpFromInvitation(ctx context.Context, input types
errInvitationNotFound *iam.ErrInvitationNotFound errInvitationNotFound *iam.ErrInvitationNotFound
errInvitationAlreadyAccepted *iam.ErrInvitationAlreadyAccepted errInvitationAlreadyAccepted *iam.ErrInvitationAlreadyAccepted
errInvitationExpired *iam.ErrInvitationExpired errInvitationExpired *iam.ErrInvitationExpired
errUserAlreadyExists *iam.ErrUserAlreadyExists errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists
isInvalidErr = errors.As(err, &errInvalidToken) || isInvalidErr = errors.As(err, &errInvalidToken) ||
errors.As(err, &errInvitationNotFound) || errors.As(err, &errInvitationNotFound) ||
@@ -299,7 +299,7 @@ func (r *mutationResolver) SignUpFromInvitation(ctx context.Context, input types
return nil, gqlutils.Invalid(err, nil) return nil, gqlutils.Invalid(err, nil)
} }
if errors.As(err, &errUserAlreadyExists) { if errors.As(err, &errIdentityAlreadyExists) {
return nil, gqlutils.Conflict(err) return nil, gqlutils.Conflict(err)
} }
@@ -371,7 +371,7 @@ func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEm
if err != nil { if err != nil {
var ( var (
errInvalidToken *iam.ErrInvalidToken errInvalidToken *iam.ErrInvalidToken
errUserNotFound *iam.ErrUserNotFound errIdentityNotFound *iam.ErrIdentityNotFound
errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified
errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch
@@ -387,7 +387,7 @@ func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEm
return nil, gqlutils.Conflict(err) return nil, gqlutils.Conflict(err)
} }
if errors.As(err, &errUserNotFound) { if errors.As(err, &errIdentityNotFound) {
return nil, gqlutils.NotFound(err) return nil, gqlutils.NotFound(err)
} }
@@ -402,7 +402,7 @@ func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEm
// ChangePassword is the resolver for the changePassword field. // ChangePassword is the resolver for the changePassword field.
func (r *mutationResolver) ChangePassword(ctx context.Context, input types.ChangePasswordInput) (*types.ChangePasswordPayload, error) { func (r *mutationResolver) ChangePassword(ctx context.Context, input types.ChangePasswordInput) (*types.ChangePasswordPayload, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
err := r.iam.AccountService.ChangePassword( err := r.iam.AccountService.ChangePassword(
ctx, ctx,
@@ -414,15 +414,15 @@ func (r *mutationResolver) ChangePassword(ctx context.Context, input types.Chang
) )
if err != nil { if err != nil {
var ( var (
errInvalidPassword *iam.ErrInvalidPassword errInvalidPassword *iam.ErrInvalidPassword
errUserNotFound *iam.ErrUserNotFound errIdentityNotFound *iam.ErrIdentityNotFound
) )
if errors.As(err, &errInvalidPassword) { if errors.As(err, &errInvalidPassword) {
return nil, gqlutils.Invalid(err, nil) return nil, gqlutils.Invalid(err, nil)
} }
if errors.As(err, &errUserNotFound) { if errors.As(err, &errIdentityNotFound) {
return nil, gqlutils.NotFound(err) return nil, gqlutils.NotFound(err)
} }
@@ -437,7 +437,7 @@ func (r *mutationResolver) ChangePassword(ctx context.Context, input types.Chang
// ChangeEmail is the resolver for the changeEmail field. // ChangeEmail is the resolver for the changeEmail field.
func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEmailInput) (*types.ChangeEmailPayload, error) { func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEmailInput) (*types.ChangeEmailPayload, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
err := r.iam.AccountService.ChangeEmail( err := r.iam.AccountService.ChangeEmail(
ctx, ctx,
@@ -449,15 +449,15 @@ func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEm
) )
if err != nil { if err != nil {
var ( var (
errInvalidPassword *iam.ErrInvalidPassword errInvalidPassword *iam.ErrInvalidPassword
errUserNotFound *iam.ErrUserNotFound errIdentityNotFound *iam.ErrIdentityNotFound
) )
if errors.As(err, &errInvalidPassword) { if errors.As(err, &errInvalidPassword) {
return nil, gqlutils.Invalid(err, nil) return nil, gqlutils.Invalid(err, nil)
} }
if errors.As(err, &errUserNotFound) { if errors.As(err, &errIdentityNotFound) {
return nil, gqlutils.NotFound(err) return nil, gqlutils.NotFound(err)
} }
@@ -522,7 +522,7 @@ func (r *mutationResolver) UpdateIdentityProfile(ctx context.Context, input type
// RevokeSession is the resolver for the revokeSession field. // RevokeSession is the resolver for the revokeSession field.
func (r *mutationResolver) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) { func (r *mutationResolver) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID) err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID)
if err != nil { if err != nil {
@@ -553,7 +553,7 @@ func (r *mutationResolver) RevokeAllSessions(ctx context.Context) (*types.Revoke
// CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field. // CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field.
func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) { func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
userAPIKey, token, err := r.iam.AccountService.CreatePersonalAPIKey( userAPIKey, token, err := r.iam.AccountService.CreatePersonalAPIKey(
ctx, ctx,
@@ -567,7 +567,7 @@ func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types
} }
return &types.CreatePersonalAPIKeyPayload{ return &types.CreatePersonalAPIKeyPayload{
PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.UserAPIKeyOrderFieldCreatedAt), PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.PersonalAPIKeyOrderFieldCreatedAt),
Token: token, Token: token,
}, nil }, nil
} }
@@ -579,7 +579,7 @@ func (r *mutationResolver) UpdatePersonalAPIKey(ctx context.Context, input types
// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field. // RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field.
func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) { func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.TokenID) err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.TokenID)
if err != nil { if err != nil {
@@ -592,7 +592,7 @@ func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types
// CreateOrganization is the resolver for the createOrganization field. // CreateOrganization is the resolver for the createOrganization field.
func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) { func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
var ( var (
logoFile *iam.UploadedFile logoFile *iam.UploadedFile
@@ -735,7 +735,7 @@ func (r *mutationResolver) RemoveMember(ctx context.Context, input types.RemoveM
// AcceptInvitation is the resolver for the acceptInvitation field. // AcceptInvitation is the resolver for the acceptInvitation field.
func (r *mutationResolver) AcceptInvitation(ctx context.Context, input types.AcceptInvitationInput) (*types.AcceptInvitationPayload, error) { func (r *mutationResolver) AcceptInvitation(ctx context.Context, input types.AcceptInvitationInput) (*types.AcceptInvitationPayload, error) {
identity := UserFromContext(ctx) identity := IdentityFromContext(ctx)
membership, err := r.iam.AccountService.AcceptInvitation(ctx, identity.ID, input.InvitationID) membership, err := r.iam.AccountService.AcceptInvitation(ctx, identity.ID, input.InvitationID)
if err != nil { if err != nil {
@@ -928,7 +928,7 @@ func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
var ( var (
loadNode func(ctx context.Context, id gid.GID) (types.Node, error) loadNode func(ctx context.Context, id gid.GID) (types.Node, error)
user = UserFromContext(ctx) user = IdentityFromContext(ctx)
action string action string
) )
@@ -942,7 +942,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
} }
return types.NewOrganization(organization), nil return types.NewOrganization(organization), nil
} }
case coredata.UserEntityType: case coredata.IdentityEntityType:
action = iam.ActionIAMIdentityGet action = iam.ActionIAMIdentityGet
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
identity, err := r.iam.AccountService.GetIdentity(ctx, id) identity, err := r.iam.AccountService.GetIdentity(ctx, id)
@@ -1009,7 +1009,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
if err != nil { if err != nil {
var ( var (
errOrganizationNotFound *iam.ErrOrganizationNotFound errOrganizationNotFound *iam.ErrOrganizationNotFound
errIdentityNotFound *iam.ErrUserNotFound errIdentityNotFound *iam.ErrIdentityNotFound
errSessionNotFound *iam.ErrSessionNotFound errSessionNotFound *iam.ErrSessionNotFound
errMembershipNotFound *iam.ErrMembershipNotFound errMembershipNotFound *iam.ErrMembershipNotFound
errInvitationNotFound *iam.ErrInvitationNotFound errInvitationNotFound *iam.ErrInvitationNotFound
@@ -1034,7 +1034,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
// Viewer is the resolver for the viewer field. // Viewer is the resolver for the viewer field.
func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) { func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) {
user := UserFromContext(ctx) user := IdentityFromContext(ctx)
return &types.Identity{ return &types.Identity{
ID: user.ID, ID: user.ID,

View File

@@ -52,7 +52,7 @@ type (
) )
func ensureAuthenticated(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { func ensureAuthenticated(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
if identity == nil { if identity == nil {
return func(ctx context.Context) *graphql.Response { return func(ctx context.Context) *graphql.Response {
@@ -228,7 +228,7 @@ func NewMux(
panic(fmt.Errorf("cannot parse organization id: %w", err)) panic(fmt.Errorf("cannot parse organization id: %w", err))
} }
identity := connect_v1.UserFromContext(r.Context()) identity := connect_v1.IdentityFromContext(r.Context())
apiKey := connect_v1.APIKeyFromContext(r.Context()) apiKey := connect_v1.APIKeyFromContext(r.Context())
if identity == nil { if identity == nil {
httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("authentication required")) httpserver.RenderError(w, http.StatusUnauthorized, fmt.Errorf("authentication required"))
@@ -328,7 +328,7 @@ func GetTenantService(ctx context.Context, proboSvc *probo.Service, tenantID gid
} }
func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action iam.Action) { func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action iam.Action) {
user := connect_v1.UserFromContext(ctx) user := connect_v1.IdentityFromContext(ctx)
apiKey := connect_v1.APIKeyFromContext(ctx) apiKey := connect_v1.APIKeyFromContext(ctx)
var credentialID *gid.GID var credentialID *gid.GID

View File

@@ -967,7 +967,7 @@ func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.Doc
func (r *documentVersionResolver) Signed(ctx context.Context, obj *types.DocumentVersion) (bool, error) { func (r *documentVersionResolver) Signed(ctx context.Context, obj *types.DocumentVersion) (bool, error) {
r.MustBeAuthorized(ctx, obj.ID, iam.ActionGetSigned) r.MustBeAuthorized(ctx, obj.ID, iam.ActionGetSigned)
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
if identity == nil { if identity == nil {
panic(fmt.Errorf("user not found in context")) panic(fmt.Errorf("user not found in context"))
} }
@@ -1758,7 +1758,7 @@ func (r *mutationResolver) DeleteTrustCenterFile(ctx context.Context, input type
// CreatePeople is the resolver for the createPeople field. // CreatePeople is the resolver for the createPeople field.
func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreatePeopleInput) (*types.CreatePeoplePayload, error) { func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreatePeopleInput) (*types.CreatePeoplePayload, error) {
user := connect_v1.UserFromContext(ctx) user := connect_v1.IdentityFromContext(ctx)
r.iam.Authorizer.Authorize(ctx, iam.AuthorizeParams{ r.iam.Authorizer.Authorize(ctx, iam.AuthorizeParams{
Principal: user.ID, Principal: user.ID,
@@ -2165,7 +2165,7 @@ func (r *mutationResolver) ExportFramework(ctx context.Context, input types.Expo
r.MustBeAuthorized(ctx, input.FrameworkID, iam.ActionExportFramework) r.MustBeAuthorized(ctx, input.FrameworkID, iam.ActionExportFramework)
prb := r.ProboService(ctx, input.FrameworkID.TenantID()) prb := r.ProboService(ctx, input.FrameworkID.TenantID())
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
err, exportJobID := prb.Frameworks.RequestExport( err, exportJobID := prb.Frameworks.RequestExport(
ctx, ctx,
@@ -3257,7 +3257,7 @@ func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input typ
prb := r.ProboService(ctx, input.DocumentID.TenantID()) prb := r.ProboService(ctx, input.DocumentID.TenantID())
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
document, documentVersion, err := prb.Documents.PublishVersion(ctx, input.DocumentID, identity.ID, input.Changelog) document, documentVersion, err := prb.Documents.PublishVersion(ctx, input.DocumentID, identity.ID, input.Changelog)
if err != nil { if err != nil {
@@ -3287,7 +3287,7 @@ func (r *mutationResolver) BulkPublishDocumentVersions(ctx context.Context, inpu
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
documentVersions, documents, err := prb.Documents.BulkPublishVersions( documentVersions, documents, err := prb.Documents.BulkPublishVersions(
ctx, ctx,
@@ -3343,7 +3343,7 @@ func (r *mutationResolver) BulkExportDocuments(ctx context.Context, input types.
prb := r.ProboService(ctx, input.DocumentIds[0].TenantID()) prb := r.ProboService(ctx, input.DocumentIds[0].TenantID())
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
options := probo.ExportPDFOptions{ options := probo.ExportPDFOptions{
WithWatermark: input.WithWatermark, WithWatermark: input.WithWatermark,
@@ -3514,7 +3514,7 @@ func (r *mutationResolver) CancelSignatureRequest(ctx context.Context, input typ
func (r *mutationResolver) SignDocument(ctx context.Context, input types.SignDocumentInput) (*types.SignDocumentPayload, error) { func (r *mutationResolver) SignDocument(ctx context.Context, input types.SignDocumentInput) (*types.SignDocumentPayload, error) {
r.MustBeAuthorized(ctx, input.DocumentVersionID, iam.ActionSignDocument) r.MustBeAuthorized(ctx, input.DocumentVersionID, iam.ActionSignDocument)
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
prb := r.ProboService(ctx, input.DocumentVersionID.TenantID()) prb := r.ProboService(ctx, input.DocumentVersionID.TenantID())
documentVersionSignature, err := prb.Documents.SignDocumentVersionByEmail(ctx, input.DocumentVersionID, identity.EmailAddress) documentVersionSignature, err := prb.Documents.SignDocumentVersionByEmail(ctx, input.DocumentVersionID, identity.EmailAddress)
@@ -3564,7 +3564,7 @@ func (r *mutationResolver) ExportSignableVersionDocumentPDF(ctx context.Context,
panic(fmt.Errorf("cannot get document version: %w", err)) panic(fmt.Errorf("cannot get document version: %w", err))
} }
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
documentFilter := coredata.NewDocumentFilter(nil).WithUserEmail(&identity.EmailAddress) documentFilter := coredata.NewDocumentFilter(nil).WithUserEmail(&identity.EmailAddress)
_, err = prb.Documents.GetWithFilter(ctx, documentVersion.DocumentID, documentFilter) _, err = prb.Documents.GetWithFilter(ctx, documentVersion.DocumentID, documentFilter)
@@ -5919,7 +5919,8 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
// Viewer is the resolver for the viewer field. // Viewer is the resolver for the viewer field.
func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) { func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) {
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
session := connect_v1.SessionFromContext(ctx) session := connect_v1.SessionFromContext(ctx)
apiKey := connect_v1.APIKeyFromContext(ctx) apiKey := connect_v1.APIKeyFromContext(ctx)

View File

@@ -41,7 +41,7 @@ func RequireAPIKeyHandler(
) )
apiKey := connect_v1.APIKeyFromContext(ctx) apiKey := connect_v1.APIKeyFromContext(ctx)
identity := connect_v1.UserFromContext(ctx) identity := connect_v1.IdentityFromContext(ctx)
if identity == nil { if identity == nil {
httpserver.RenderError(w, http.StatusUnauthorized, errors.New("authentication required")) httpserver.RenderError(w, http.StatusUnauthorized, errors.New("authentication required"))
return return

View File

@@ -19,7 +19,7 @@ type Resolver struct {
} }
func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action iam.Action) { func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action iam.Action) {
user := connect_v1.UserFromContext(ctx) user := connect_v1.IdentityFromContext(ctx)
apiKey := connect_v1.APIKeyFromContext(ctx) apiKey := connect_v1.APIKeyFromContext(ctx)
if user == nil { if user == nil {
panic(&iam.TenantAccessError{Message: "authentication required"}) panic(&iam.TenantAccessError{Message: "authentication required"})

View File

@@ -21,7 +21,7 @@ import (
// ListOrganizationsTool handles the listOrganizations tool // ListOrganizationsTool handles the listOrganizations tool
// List all organizations the user has access to // List all organizations the user has access to
func (r *Resolver) ListOrganizationsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListOrganizationsInput) (*mcp.CallToolResult, types.ListOrganizationsOutput, error) { func (r *Resolver) ListOrganizationsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListOrganizationsInput) (*mcp.CallToolResult, types.ListOrganizationsOutput, error) {
user := connect_v1.UserFromContext(ctx) user := connect_v1.IdentityFromContext(ctx)
organizations, err := r.iamSvc.AccountService.ListOrganizations(ctx, user.ID) organizations, err := r.iamSvc.AccountService.ListOrganizations(ctx, user.ID)
if err != nil { if err != nil {
@@ -1667,7 +1667,7 @@ func (r *Resolver) PublishDocumentVersionTool(ctx context.Context, req *mcp.Call
svc := r.ProboService(ctx, input.DocumentID) svc := r.ProboService(ctx, input.DocumentID)
user := connect_v1.UserFromContext(ctx) user := connect_v1.IdentityFromContext(ctx)
document, documentVersion, err := svc.Documents.PublishVersion(ctx, input.DocumentID, user.ID, input.Changelog) document, documentVersion, err := svc.Documents.PublishVersion(ctx, input.DocumentID, user.ID, input.Changelog)
if err != nil { if err != nil {

View File

@@ -33,7 +33,7 @@ type TokenAccessData struct {
} }
type ContextAccessor interface { type ContextAccessor interface {
UserFromContext(ctx context.Context) *coredata.User IdentityFromContext(ctx context.Context) *coredata.Identity
TokenAccessFromContext(ctx context.Context) *TokenAccessData TokenAccessFromContext(ctx context.Context) *TokenAccessData
} }
@@ -46,8 +46,8 @@ func ValidateTenantAccess(ctx context.Context, accessor ContextAccessor, userTen
return nil return nil
} }
user := accessor.UserFromContext(ctx) identity := accessor.IdentityFromContext(ctx)
if user != nil { if identity != nil {
userTenants, ok := ctx.Value(userTenantContextKey).(*[]gid.TenantID) userTenants, ok := ctx.Value(userTenantContextKey).(*[]gid.TenantID)
if !ok || userTenants == nil { if !ok || userTenants == nil {
return fmt.Errorf("access denied: no tenant information available") return fmt.Errorf("access denied: no tenant information available")
@@ -65,10 +65,10 @@ func ValidateTenantAccess(ctx context.Context, accessor ContextAccessor, userTen
} }
func GetCurrentUserRole(ctx context.Context, accessor ContextAccessor) types.Role { func GetCurrentUserRole(ctx context.Context, accessor ContextAccessor) types.Role {
user := accessor.UserFromContext(ctx) identity := accessor.IdentityFromContext(ctx)
tokenAccess := accessor.TokenAccessFromContext(ctx) tokenAccess := accessor.TokenAccessFromContext(ctx)
if user != nil || tokenAccess != nil { if identity != nil || tokenAccess != nil {
return types.RoleUser return types.RoleUser
} }
return types.RoleNone return types.RoleNone

View File

@@ -74,7 +74,7 @@ func (r *documentResolver) IsUserAuthorized(ctx context.Context, obj *types.Docu
// return false, nil // return false, nil
// } // }
// userData := connect_v1.UserFromContext(ctx) // userData := connect_v1.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return true, nil // return true, nil
// } // }
@@ -99,7 +99,7 @@ func (r *documentResolver) HasUserRequestedAccess(ctx context.Context, obj *type
// return false, nil // return false, nil
// } // }
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return false, nil // return false, nil
// } // }
@@ -137,7 +137,7 @@ func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framewor
func (r *mutationResolver) RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error) { func (r *mutationResolver) RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error) {
// publicTrustService := r.PublicTrustService(ctx, input.TrustCenterID.TenantID()) // publicTrustService := r.PublicTrustService(ctx, input.TrustCenterID.TenantID())
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return nil, fmt.Errorf("session users cannot request trust center access") // return nil, fmt.Errorf("session users cannot request trust center access")
// } // }
@@ -239,7 +239,7 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex
// } // }
// } // }
// userData := UserFromContext(ctx) // userData := IdentityFromContext(ctx)
// var userEmail mail.Addr // var userEmail mail.Addr
// if userData != nil { // if userData != nil {
// userEmail = userData.EmailAddress // userEmail = userData.EmailAddress
@@ -319,7 +319,7 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo
// } // }
// } // }
// userData := UserFromContext(ctx) // userData := IdentityFromContext(ctx)
// var userEmail mail.Addr // var userEmail mail.Addr
// if userData != nil { // if userData != nil {
// userEmail = userData.EmailAddress // userEmail = userData.EmailAddress
@@ -371,7 +371,7 @@ func (r *mutationResolver) RequestDocumentAccess(ctx context.Context, input type
// fullname string // fullname string
// ) // )
// identity := connect_v1.UserFromContext(ctx) // identity := connect_v1.IdentityFromContext(ctx)
// if identity != nil { // if identity != nil {
// email = identity.EmailAddress // email = identity.EmailAddress
// fullname = identity.FullName // fullname = identity.FullName
@@ -422,7 +422,7 @@ func (r *mutationResolver) RequestReportAccess(ctx context.Context, input types.
// return nil, fmt.Errorf("report is publicly available and does not require access request") // return nil, fmt.Errorf("report is publicly available and does not require access request")
// } // }
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return nil, fmt.Errorf("session users cannot request trust center access") // return nil, fmt.Errorf("session users cannot request trust center access")
// } // }
@@ -473,7 +473,7 @@ func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, inp
// return nil, fmt.Errorf("trust center file is publicly available and does not require access request") // return nil, fmt.Errorf("trust center file is publicly available and does not require access request")
// } // }
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return nil, fmt.Errorf("session users cannot request trust center access") // return nil, fmt.Errorf("session users cannot request trust center access")
// } // }
@@ -571,7 +571,7 @@ func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input type
// } // }
// } // }
// userData := UserFromContext(ctx) // userData := IdentityFromContext(ctx)
// var userEmail mail.Addr // var userEmail mail.Addr
// if userData != nil { // if userData != nil {
// userEmail = userData.EmailAddress // userEmail = userData.EmailAddress
@@ -753,7 +753,7 @@ func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report
// return false, nil // return false, nil
// } // }
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return true, nil // return true, nil
// } // }
@@ -780,7 +780,7 @@ func (r *reportResolver) HasUserRequestedAccess(ctx context.Context, obj *types.
// return false, nil // return false, nil
// } // }
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return false, nil // return false, nil
// } // }
@@ -836,7 +836,7 @@ func (r *trustCenterResolver) HasAcceptedNonDisclosureAgreement(ctx context.Cont
// return false, nil // return false, nil
// } // }
// userData := UserFromContext(ctx) // userData := IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return true, nil // return true, nil
// } // }
@@ -963,7 +963,7 @@ func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *typ
// return false, nil // return false, nil
// } // }
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return true, nil // return true, nil
// } // }
@@ -990,7 +990,7 @@ func (r *trustCenterFileResolver) HasUserRequestedAccess(ctx context.Context, ob
// return false, nil // return false, nil
// } // }
// userData := r.UserFromContext(ctx) // userData := r.IdentityFromContext(ctx)
// if userData != nil { // if userData != nil {
// return false, nil // return false, nil
// } // }