Skip to content

Commit d4c72f1

Browse files
authored
refactor(store): wave B.8 — identity_provider repo (read side) (#259) (#260)
* refactor(store): wave B.8 — identity_provider repo (read side) (#259) IdentityProviderRepo covers the read side of the identity-providers projection: Get / GetBySlug / GetBySlugForSCIM / List / Count / ListEnabled. All 17 handler/scim/idp-internal read sites migrated. ClientSecretEncrypted stays encrypted at the boundary; the handler decrypts via internal/crypto when initiating the OIDC dance. ScimTokenHash is surfaced because the SCIM auth handler validates incoming bearer tokens against this column. Field renames at the type boundary follow Go convention: IssuerUrl → IssuerURL, AuthorizationUrl → AuthorizationURL, etc. Propagated to all consumers including sso_handler's OIDC provider config and idpToProto. Call sites migrated (17): - internal/api/idp_handler.go (13) + idpToProto signature - internal/api/sso_handler.go (3) + URL field renames - internal/scim/auth.go (1) - internal/idp/linker.go — LinkOrCreate signature shifted to store.IdentityProvider (callers in api/sso updated accordingly) - internal/idp/linker_test.go — test fixtures use store.IdentityProvider Non-goals: - Write-side projector queries (Insert/Update/SetSCIMEnabled/ RotateSCIMToken) stay on Queries() per the established pattern; pure projector internals migrate in a later wave. - SCIM user/group mapping queries — separate SCIM-domain sub-wave. Closes #259. * fix(scim): align provider context type with new IdentityProvider repo The withAuth middleware now stores store.IdentityProvider in context but providerFromContext was still asserting db.IdentityProvidersProjection, so every authenticated SCIM request failed the type assertion and the handlers downstream returned 401. Swap the context cast and every downstream helper that takes the provider to use store.IdentityProvider. Field accesses (ID, Slug, Name, DefaultRoleID, AutoLinkByEmail, ScimTokenHash) are identical between the two types so no further site changes are needed.
1 parent ee6e5a5 commit d4c72f1

15 files changed

Lines changed: 260 additions & 69 deletions

internal/api/idp_handler.go

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/manchtools/power-manage/server/internal/eventtypes/payloads"
1919
"github.com/manchtools/power-manage/server/internal/middleware"
2020
"github.com/manchtools/power-manage/server/internal/store"
21-
db "github.com/manchtools/power-manage/server/internal/store/generated"
2221
)
2322

2423
// IDPHandler handles identity provider CRUD RPCs.
@@ -46,7 +45,7 @@ func (h *IDPHandler) CreateIdentityProvider(ctx context.Context, req *connect.Re
4645
}
4746

4847
// Check for duplicate slug
49-
_, err = h.store.Queries().GetIdentityProviderBySlug(ctx, req.Msg.Slug)
48+
_, err = h.store.Repos().IdentityProvider.GetBySlug(ctx, req.Msg.Slug)
5049
if err == nil {
5150
return nil, apiErrorCtx(ctx, ErrProviderSlugExists, connect.CodeAlreadyExists, "provider with this slug already exists")
5251
}
@@ -91,7 +90,7 @@ func (h *IDPHandler) CreateIdentityProvider(ctx context.Context, req *connect.Re
9190
return nil, err
9291
}
9392

94-
provider, err := h.store.Queries().GetIdentityProviderByID(ctx, id)
93+
provider, err := h.store.Repos().IdentityProvider.Get(ctx, id)
9594
if err != nil {
9695
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to read back provider")
9796
}
@@ -107,7 +106,7 @@ func (h *IDPHandler) GetIdentityProvider(ctx context.Context, req *connect.Reque
107106
return nil, err
108107
}
109108

110-
provider, err := h.store.Queries().GetIdentityProviderByID(ctx, req.Msg.Id)
109+
provider, err := h.store.Repos().IdentityProvider.Get(ctx, req.Msg.Id)
111110
if err != nil {
112111
return nil, handleGetError(ctx, err, ErrProviderNotFound, "provider not found")
113112
}
@@ -124,15 +123,15 @@ func (h *IDPHandler) ListIdentityProviders(ctx context.Context, req *connect.Req
124123
return nil, err
125124
}
126125

127-
providers, err := h.store.Queries().ListIdentityProviders(ctx, db.ListIdentityProvidersParams{
126+
providers, err := h.store.Repos().IdentityProvider.List(ctx, store.ListIdentityProvidersFilter{
128127
Limit: pageSize,
129128
Offset: offset,
130129
})
131130
if err != nil {
132131
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to list providers")
133132
}
134133

135-
count, err := h.store.Queries().CountIdentityProviders(ctx)
134+
count, err := h.store.Repos().IdentityProvider.Count(ctx)
136135
if err != nil {
137136
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to count providers")
138137
}
@@ -163,7 +162,7 @@ func (h *IDPHandler) UpdateIdentityProvider(ctx context.Context, req *connect.Re
163162
}
164163

165164
// Verify provider exists
166-
_, err = h.store.Queries().GetIdentityProviderByID(ctx, req.Msg.Id)
165+
_, err = h.store.Repos().IdentityProvider.Get(ctx, req.Msg.Id)
167166
if err != nil {
168167
return nil, handleGetError(ctx, err, ErrProviderNotFound, "provider not found")
169168
}
@@ -216,7 +215,7 @@ func (h *IDPHandler) UpdateIdentityProvider(ctx context.Context, req *connect.Re
216215
return nil, err
217216
}
218217

219-
provider, err := h.store.Queries().GetIdentityProviderByID(ctx, req.Msg.Id)
218+
provider, err := h.store.Repos().IdentityProvider.Get(ctx, req.Msg.Id)
220219
if err != nil {
221220
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to read back provider")
222221
}
@@ -321,7 +320,7 @@ func (h *IDPHandler) EnableSCIM(ctx context.Context, req *connect.Request[pm.Ena
321320
return nil, err
322321
}
323322

324-
provider, err := h.store.Queries().GetIdentityProviderByID(ctx, req.Msg.Id)
323+
provider, err := h.store.Repos().IdentityProvider.Get(ctx, req.Msg.Id)
325324
if err != nil {
326325
return nil, handleGetError(ctx, err, ErrProviderNotFound, "provider not found")
327326
}
@@ -374,7 +373,7 @@ func (h *IDPHandler) DisableSCIM(ctx context.Context, req *connect.Request[pm.Di
374373
return nil, err
375374
}
376375

377-
provider, err := h.store.Queries().GetIdentityProviderByID(ctx, req.Msg.Id)
376+
provider, err := h.store.Repos().IdentityProvider.Get(ctx, req.Msg.Id)
378377
if err != nil {
379378
return nil, handleGetError(ctx, err, ErrProviderNotFound, "provider not found")
380379
}
@@ -408,7 +407,7 @@ func (h *IDPHandler) RotateSCIMToken(ctx context.Context, req *connect.Request[p
408407
return nil, err
409408
}
410409

411-
provider, err := h.store.Queries().GetIdentityProviderByID(ctx, req.Msg.Id)
410+
provider, err := h.store.Repos().IdentityProvider.Get(ctx, req.Msg.Id)
412411
if err != nil {
413412
return nil, handleGetError(ctx, err, ErrProviderNotFound, "provider not found")
414413
}
@@ -449,18 +448,18 @@ func (h *IDPHandler) RotateSCIMToken(ctx context.Context, req *connect.Request[p
449448

450449
// idpToProto converts a database identity provider to a proto message.
451450
// Note: client_secret is never returned to the client.
452-
func (h *IDPHandler) idpToProto(p db.IdentityProvidersProjection) *pm.IdentityProvider {
451+
func (h *IDPHandler) idpToProto(p store.IdentityProvider) *pm.IdentityProvider {
453452
provider := &pm.IdentityProvider{
454453
Id: p.ID,
455454
Name: p.Name,
456455
Slug: p.Slug,
457456
ProviderType: identityProviderTypeFromString(p.ProviderType),
458457
Enabled: p.Enabled,
459458
ClientId: p.ClientID,
460-
IssuerUrl: p.IssuerUrl,
461-
AuthorizationUrl: p.AuthorizationUrl,
462-
TokenUrl: p.TokenUrl,
463-
UserinfoUrl: p.UserinfoUrl,
459+
IssuerUrl: p.IssuerURL,
460+
AuthorizationUrl: p.AuthorizationURL,
461+
TokenUrl: p.TokenURL,
462+
UserinfoUrl: p.UserinfoURL,
464463
Scopes: p.Scopes,
465464
AutoCreateUsers: p.AutoCreateUsers,
466465
AutoLinkByEmail: p.AutoLinkByEmail,

internal/api/sso_handler.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (h *SSOHandler) ListAuthMethods(ctx context.Context, req *connect.Request[p
8282
}
8383

8484
// List enabled providers
85-
providers, err := h.store.Queries().ListEnabledIdentityProviders(ctx)
85+
providers, err := h.store.Repos().IdentityProvider.ListEnabled(ctx)
8686
if err == nil {
8787
for _, p := range providers {
8888
resp.Providers = append(resp.Providers, &pm.AuthMethodProvider{
@@ -104,7 +104,7 @@ func (h *SSOHandler) GetSSOLoginURL(ctx context.Context, req *connect.Request[pm
104104
return nil, err
105105
}
106106

107-
provider, err := h.store.Queries().GetIdentityProviderBySlug(ctx, req.Msg.Slug)
107+
provider, err := h.store.Repos().IdentityProvider.GetBySlug(ctx, req.Msg.Slug)
108108
if err != nil {
109109
return nil, handleGetError(ctx, err, ErrProviderNotFound, "provider not found")
110110
}
@@ -159,10 +159,10 @@ func (h *SSOHandler) GetSSOLoginURL(ctx context.Context, req *connect.Request[pm
159159
callbackURL = h.callbackBaseURL + "/auth/callback/" + provider.Slug
160160
}
161161
oidcProvider, err := idp.NewOIDCProvider(ctx, idp.ProviderConfig{
162-
IssuerURL: provider.IssuerUrl,
163-
AuthorizationURL: provider.AuthorizationUrl,
164-
TokenURL: provider.TokenUrl,
165-
UserinfoURL: provider.UserinfoUrl,
162+
IssuerURL: provider.IssuerURL,
163+
AuthorizationURL: provider.AuthorizationURL,
164+
TokenURL: provider.TokenURL,
165+
UserinfoURL: provider.UserinfoURL,
166166
ClientID: provider.ClientID,
167167
ClientSecret: clientSecret,
168168
Scopes: provider.Scopes,
@@ -204,7 +204,7 @@ func (h *SSOHandler) SSOCallback(ctx context.Context, req *connect.Request[pm.SS
204204
}
205205

206206
// Get provider
207-
provider, err := h.store.Queries().GetIdentityProviderByID(ctx, authState.ProviderID)
207+
provider, err := h.store.Repos().IdentityProvider.Get(ctx, authState.ProviderID)
208208
if err != nil {
209209
return nil, apiErrorCtx(ctx, ErrInternal, connect.CodeInternal, "failed to get provider")
210210
}
@@ -232,10 +232,10 @@ func (h *SSOHandler) SSOCallback(ctx context.Context, req *connect.Request[pm.SS
232232
callbackURL = h.callbackBaseURL + "/auth/callback/" + provider.Slug
233233
}
234234
oidcProvider, err := idp.NewOIDCProvider(ctx, idp.ProviderConfig{
235-
IssuerURL: provider.IssuerUrl,
236-
AuthorizationURL: provider.AuthorizationUrl,
237-
TokenURL: provider.TokenUrl,
238-
UserinfoURL: provider.UserinfoUrl,
235+
IssuerURL: provider.IssuerURL,
236+
AuthorizationURL: provider.AuthorizationURL,
237+
TokenURL: provider.TokenURL,
238+
UserinfoURL: provider.UserinfoURL,
239239
ClientID: provider.ClientID,
240240
ClientSecret: clientSecret,
241241
Scopes: provider.Scopes,

internal/idp/linker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func NewLinker(queries Querier, appender EventAppender) *Linker {
7575
// 2. If auto_link_by_email: find user by email → create link, return user
7676
// 3. If auto_create_users: create user (no password), assign default role, create link
7777
// 4. Otherwise: error
78-
func (l *Linker) LinkOrCreate(ctx context.Context, provider db.IdentityProvidersProjection, claims *UserClaims) (*LinkResult, error) {
78+
func (l *Linker) LinkOrCreate(ctx context.Context, provider store.IdentityProvider, claims *UserClaims) (*LinkResult, error) {
7979
slog.Debug("SSO linker: starting LinkOrCreate",
8080
"provider_id", provider.ID,
8181
"provider_slug", provider.Slug,

internal/idp/linker_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestLinkOrCreate_AutoCreateUserIncludesLinuxFields(t *testing.T) {
5353
appender := &mockAppender{}
5454
linker := NewLinker(querier, appender)
5555

56-
provider := db.IdentityProvidersProjection{
56+
provider := store.IdentityProvider{
5757
ID: "provider-1",
5858
Slug: "test-idp",
5959
AutoCreateUsers: true,
@@ -105,7 +105,7 @@ func TestLinkOrCreate_AutoCreateUserDeriveUsernameFromEmail(t *testing.T) {
105105
appender := &mockAppender{}
106106
linker := NewLinker(querier, appender)
107107

108-
provider := db.IdentityProvidersProjection{
108+
provider := store.IdentityProvider{
109109
ID: "provider-2",
110110
Slug: "test-idp",
111111
AutoCreateUsers: true,

internal/scim/auth.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"golang.org/x/crypto/bcrypt"
99

1010
"github.com/manchtools/power-manage/server/internal/store"
11-
db "github.com/manchtools/power-manage/server/internal/store/generated"
1211
)
1312

1413
// withAuth wraps a handler with SCIM bearer token authentication.
@@ -56,7 +55,7 @@ func (h *Handler) withAuth(next http.HandlerFunc) http.HandlerFunc {
5655
}
5756

5857
// Look up provider by slug with SCIM enabled
59-
provider, err := h.store.Queries().GetIdentityProviderBySlugForSCIM(r.Context(), slug)
58+
provider, err := h.store.Repos().IdentityProvider.GetBySlugForSCIM(r.Context(), slug)
6059
if err != nil {
6160
if store.IsNotFound(err) {
6261
h.logger.Warn("SCIM auth failed: unknown provider or SCIM not enabled", "slug", slug)
@@ -98,7 +97,7 @@ func (h *Handler) withAuth(next http.HandlerFunc) http.HandlerFunc {
9897
}
9998

10099
// providerFromContext extracts the authenticated identity provider from the context.
101-
func providerFromContext(ctx context.Context) (db.IdentityProvidersProjection, bool) {
102-
provider, ok := ctx.Value(providerContextKey).(db.IdentityProvidersProjection)
100+
func providerFromContext(ctx context.Context) (store.IdentityProvider, bool) {
101+
provider, ok := ctx.Value(providerContextKey).(store.IdentityProvider)
103102
return provider, ok
104103
}

internal/scim/groups.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (h *Handler) listGroups(w http.ResponseWriter, r *http.Request) {
6363
}
6464

6565
// listGroupsFiltered handles filtered group list requests.
66-
func (h *Handler) listGroupsFiltered(w http.ResponseWriter, r *http.Request, provider db.IdentityProvidersProjection, filterStr string, startIndex int, baseURL string) {
66+
func (h *Handler) listGroupsFiltered(w http.ResponseWriter, r *http.Request, provider store.IdentityProvider, filterStr string, startIndex int, baseURL string) {
6767
f, err := parseFilter(filterStr)
6868
if err != nil {
6969
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid filter: %s", err))

internal/scim/groups_helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
// current member set and emits per-user UserGroupMemberAdded /
2020
// UserGroupMemberRemoved events. Idempotent — calling with the same
2121
// member set twice produces no second-round events.
22-
func (h *Handler) reconcileGroupMembers(ctx context.Context, provider db.IdentityProvidersProjection, groupID string, requestedMembers []SCIMMember) {
22+
func (h *Handler) reconcileGroupMembers(ctx context.Context, provider store.IdentityProvider, groupID string, requestedMembers []SCIMMember) {
2323
h.logger.Debug("SCIM reconcileGroupMembers", "group_id", groupID, "requested_count", len(requestedMembers))
2424
currentMemberIDs, err := h.store.Queries().ListUserGroupMemberIDs(ctx, groupID)
2525
if err != nil {

internal/scim/groups_mutate.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func (h *Handler) patchGroup(w http.ResponseWriter, r *http.Request) {
192192
}
193193

194194
// handleGroupPatchAdd processes an "add" patch operation on a group.
195-
func (h *Handler) handleGroupPatchAdd(ctx context.Context, provider db.IdentityProvidersProjection, groupID string, op SCIMPatchOp) {
195+
func (h *Handler) handleGroupPatchAdd(ctx context.Context, provider store.IdentityProvider, groupID string, op SCIMPatchOp) {
196196
path := strings.ToLower(op.Path)
197197
if path != "members" && path != "" {
198198
return
@@ -216,7 +216,7 @@ func (h *Handler) handleGroupPatchAdd(ctx context.Context, provider db.IdentityP
216216
}
217217

218218
// handleGroupPatchRemove processes a "remove" patch operation on a group.
219-
func (h *Handler) handleGroupPatchRemove(ctx context.Context, provider db.IdentityProvidersProjection, groupID string, op SCIMPatchOp) {
219+
func (h *Handler) handleGroupPatchRemove(ctx context.Context, provider store.IdentityProvider, groupID string, op SCIMPatchOp) {
220220
path := strings.ToLower(op.Path)
221221

222222
// Handle path like: members[value eq "userId"]
@@ -261,7 +261,7 @@ func (h *Handler) handleGroupPatchRemove(ctx context.Context, provider db.Identi
261261
}
262262

263263
// handleGroupPatchReplace processes a "replace" patch operation on a group.
264-
func (h *Handler) handleGroupPatchReplace(ctx context.Context, provider db.IdentityProvidersProjection, groupID string, mapping db.ScimGroupMappingProjection, op SCIMPatchOp) {
264+
func (h *Handler) handleGroupPatchReplace(ctx context.Context, provider store.IdentityProvider, groupID string, mapping db.ScimGroupMappingProjection, op SCIMPatchOp) {
265265
path := strings.ToLower(op.Path)
266266

267267
switch path {

internal/scim/users.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (h *Handler) listUsers(w http.ResponseWriter, r *http.Request) {
8888
}
8989

9090
// listUsersFiltered handles filtered user list requests.
91-
func (h *Handler) listUsersFiltered(w http.ResponseWriter, r *http.Request, provider db.IdentityProvidersProjection, filterStr string, startIndex, count int, baseURL string) {
91+
func (h *Handler) listUsersFiltered(w http.ResponseWriter, r *http.Request, provider store.IdentityProvider, filterStr string, startIndex, count int, baseURL string) {
9292
f, err := parseFilter(filterStr)
9393
if err != nil {
9494
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid filter: %s", err))

internal/scim/users_helpers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func newULID() string {
3131

3232
// syncUserFromSCIM syncs email, active status, profile, and identity link data from SCIM.
3333
// SCIM is treated as the source of truth — any differences are overwritten.
34-
func (h *Handler) syncUserFromSCIM(ctx context.Context, provider db.IdentityProvidersProjection, userID, email string, active *bool, name *SCIMName) {
34+
func (h *Handler) syncUserFromSCIM(ctx context.Context, provider store.IdentityProvider, userID, email string, active *bool, name *SCIMName) {
3535
user, err := h.store.Queries().GetUserByID(ctx, userID)
3636
if err != nil {
3737
h.logger.Error("failed to get user for SCIM sync", "user_id", userID, "error", err)
@@ -97,7 +97,7 @@ func (h *Handler) syncUserFromSCIM(ctx context.Context, provider db.IdentityProv
9797

9898
// syncIdentityLink updates the identity link's external_email and external_name
9999
// to reflect the latest data from the SCIM provider (source of truth).
100-
func (h *Handler) syncIdentityLink(ctx context.Context, provider db.IdentityProvidersProjection, userID, email string, name *SCIMName) {
100+
func (h *Handler) syncIdentityLink(ctx context.Context, provider store.IdentityProvider, userID, email string, name *SCIMName) {
101101
link, err := h.store.Queries().GetIdentityLinkByProviderAndUser(ctx, db.GetIdentityLinkByProviderAndUserParams{
102102
ProviderID: provider.ID,
103103
UserID: userID,

0 commit comments

Comments
 (0)