From b34aded376160bfa58d00c09970dff82e0b9ebd0 Mon Sep 17 00:00:00 2001 From: boojack Date: Sat, 17 Jun 2023 22:35:17 +0800 Subject: [PATCH] refactor: migration idp api (#1842) * refactor: migration idp api * chore: update --- api/idp.go | 58 ------ api/v1/common.go | 24 +++ {server => api/v1}/idp.go | 107 ++++++++--- api/v1/user.go | 25 +++ api/v1/v1.go | 15 +- server/jwt.go | 24 +-- server/server.go | 7 +- store/user.go | 205 +++++++++++++++++++++ test/store/idp_test.go | 48 +++++ web/src/components/Settings/SSOSection.tsx | 4 +- web/src/helpers/api.ts | 8 +- web/src/pages/Auth.tsx | 4 +- 12 files changed, 410 insertions(+), 119 deletions(-) delete mode 100644 api/idp.go create mode 100644 api/v1/common.go rename {server => api/v1}/idp.go (69%) create mode 100644 api/v1/user.go create mode 100644 test/store/idp_test.go diff --git a/api/idp.go b/api/idp.go deleted file mode 100644 index bfffbfcf..00000000 --- a/api/idp.go +++ /dev/null @@ -1,58 +0,0 @@ -package api - -type IdentityProviderType string - -const ( - IdentityProviderOAuth2 IdentityProviderType = "OAUTH2" -) - -type IdentityProviderConfig struct { - OAuth2Config *IdentityProviderOAuth2Config `json:"oauth2Config"` -} - -type IdentityProviderOAuth2Config struct { - ClientID string `json:"clientId"` - ClientSecret string `json:"clientSecret"` - AuthURL string `json:"authUrl"` - TokenURL string `json:"tokenUrl"` - UserInfoURL string `json:"userInfoUrl"` - Scopes []string `json:"scopes"` - FieldMapping *FieldMapping `json:"fieldMapping"` -} - -type FieldMapping struct { - Identifier string `json:"identifier"` - DisplayName string `json:"displayName"` - Email string `json:"email"` -} - -type IdentityProvider struct { - ID int `json:"id"` - Name string `json:"name"` - Type IdentityProviderType `json:"type"` - IdentifierFilter string `json:"identifierFilter"` - Config *IdentityProviderConfig `json:"config"` -} - -type IdentityProviderCreate struct { - Name string `json:"name"` - Type IdentityProviderType `json:"type"` - IdentifierFilter string `json:"identifierFilter"` - Config *IdentityProviderConfig `json:"config"` -} - -type IdentityProviderFind struct { - ID *int -} - -type IdentityProviderPatch struct { - ID int - Type IdentityProviderType `json:"type"` - Name *string `json:"name"` - IdentifierFilter *string `json:"identifierFilter"` - Config *IdentityProviderConfig `json:"config"` -} - -type IdentityProviderDelete struct { - ID int -} diff --git a/api/v1/common.go b/api/v1/common.go new file mode 100644 index 00000000..d6a7ff4e --- /dev/null +++ b/api/v1/common.go @@ -0,0 +1,24 @@ +package v1 + +// UnknownID is the ID for unknowns. +const UnknownID = -1 + +// RowStatus is the status for a row. +type RowStatus string + +const ( + // Normal is the status for a normal row. + Normal RowStatus = "NORMAL" + // Archived is the status for an archived row. + Archived RowStatus = "ARCHIVED" +) + +func (e RowStatus) String() string { + switch e { + case Normal: + return "NORMAL" + case Archived: + return "ARCHIVED" + } + return "" +} diff --git a/server/idp.go b/api/v1/idp.go similarity index 69% rename from server/idp.go rename to api/v1/idp.go index cae936f4..650c7ac2 100644 --- a/server/idp.go +++ b/api/v1/idp.go @@ -1,4 +1,4 @@ -package server +package v1 import ( "encoding/json" @@ -7,12 +7,60 @@ import ( "strconv" "github.com/labstack/echo/v4" - "github.com/usememos/memos/api" "github.com/usememos/memos/common" "github.com/usememos/memos/store" ) -func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { +type IdentityProviderType string + +const ( + IdentityProviderOAuth2 IdentityProviderType = "OAUTH2" +) + +type IdentityProviderConfig struct { + OAuth2Config *IdentityProviderOAuth2Config `json:"oauth2Config"` +} + +type IdentityProviderOAuth2Config struct { + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` + AuthURL string `json:"authUrl"` + TokenURL string `json:"tokenUrl"` + UserInfoURL string `json:"userInfoUrl"` + Scopes []string `json:"scopes"` + FieldMapping *FieldMapping `json:"fieldMapping"` +} + +type FieldMapping struct { + Identifier string `json:"identifier"` + DisplayName string `json:"displayName"` + Email string `json:"email"` +} + +type IdentityProvider struct { + ID int `json:"id"` + Name string `json:"name"` + Type IdentityProviderType `json:"type"` + IdentifierFilter string `json:"identifierFilter"` + Config *IdentityProviderConfig `json:"config"` +} + +type IdentityProviderCreate struct { + Name string `json:"name"` + Type IdentityProviderType `json:"type"` + IdentifierFilter string `json:"identifierFilter"` + Config *IdentityProviderConfig `json:"config"` +} + +type IdentityProviderPatch struct { + ID int + Type IdentityProviderType `json:"type"` + Name *string `json:"name"` + IdentifierFilter *string `json:"identifierFilter"` + Config *IdentityProviderConfig `json:"config"` +} + +func (s *APIV1Service) registerIdentityProviderRoutes(g *echo.Group) { g.POST("/idp", func(c echo.Context) error { ctx := c.Request().Context() userID, ok := c.Get(getUserIDContextKey()).(int) @@ -20,17 +68,17 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") } - user, err := s.Store.FindUser(ctx, &api.UserFind{ + user, err := s.Store.GetUser(ctx, &store.FindUserMessage{ ID: &userID, }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err) } - if user == nil || user.Role != api.Host { + if user == nil || user.Role != store.Host { return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } - identityProviderCreate := &api.IdentityProviderCreate{} + identityProviderCreate := &IdentityProviderCreate{} if err := json.NewDecoder(c.Request().Body).Decode(identityProviderCreate); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post identity provider request").SetInternal(err) } @@ -44,7 +92,7 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create identity provider").SetInternal(err) } - return c.JSON(http.StatusOK, composeResponse(convertIdentityProviderFromStore(identityProviderMessage))) + return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProviderMessage)) }) g.PATCH("/idp/:idpId", func(c echo.Context) error { @@ -54,13 +102,13 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") } - user, err := s.Store.FindUser(ctx, &api.UserFind{ + user, err := s.Store.GetUser(ctx, &store.FindUserMessage{ ID: &userID, }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err) } - if user == nil || user.Role != api.Host { + if user == nil || user.Role != store.Host { return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } @@ -69,7 +117,7 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err) } - identityProviderPatch := &api.IdentityProviderPatch{ + identityProviderPatch := &IdentityProviderPatch{ ID: identityProviderID, } if err := json.NewDecoder(c.Request().Body).Decode(identityProviderPatch); err != nil { @@ -86,7 +134,7 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch identity provider").SetInternal(err) } - return c.JSON(http.StatusOK, composeResponse(convertIdentityProviderFromStore(identityProviderMessage))) + return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProviderMessage)) }) g.GET("/idp", func(c echo.Context) error { @@ -99,18 +147,18 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { userID, ok := c.Get(getUserIDContextKey()).(int) isHostUser := false if ok { - user, err := s.Store.FindUser(ctx, &api.UserFind{ + user, err := s.Store.GetUser(ctx, &store.FindUserMessage{ ID: &userID, }) - if err != nil && common.ErrorCode(err) != common.NotFound { + if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err) } - if user != nil && user.Role == api.Host { + if user == nil || user.Role == store.Host { isHostUser = true } } - identityProviderList := []*api.IdentityProvider{} + identityProviderList := []*IdentityProvider{} for _, identityProviderMessage := range identityProviderMessageList { identityProvider := convertIdentityProviderFromStore(identityProviderMessage) // data desensitize @@ -119,7 +167,7 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { } identityProviderList = append(identityProviderList, identityProvider) } - return c.JSON(http.StatusOK, composeResponse(identityProviderList)) + return c.JSON(http.StatusOK, identityProviderList) }) g.GET("/idp/:idpId", func(c echo.Context) error { @@ -129,14 +177,13 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") } - user, err := s.Store.FindUser(ctx, &api.UserFind{ + user, err := s.Store.GetUser(ctx, &store.FindUserMessage{ ID: &userID, }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err) } - // We should only show identity provider list to host user. - if user == nil || user.Role != api.Host { + if user == nil || user.Role != store.Host { return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } @@ -150,7 +197,7 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get identity provider").SetInternal(err) } - return c.JSON(http.StatusOK, composeResponse(convertIdentityProviderFromStore(identityProviderMessage))) + return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProviderMessage)) }) g.DELETE("/idp/:idpId", func(c echo.Context) error { @@ -160,13 +207,13 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session") } - user, err := s.Store.FindUser(ctx, &api.UserFind{ + user, err := s.Store.GetUser(ctx, &store.FindUserMessage{ ID: &userID, }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err) } - if user == nil || user.Role != api.Host { + if user == nil || user.Role != store.Host { return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized") } @@ -185,26 +232,26 @@ func (s *Server) registerIdentityProviderRoutes(g *echo.Group) { }) } -func convertIdentityProviderFromStore(identityProviderMessage *store.IdentityProviderMessage) *api.IdentityProvider { - return &api.IdentityProvider{ +func convertIdentityProviderFromStore(identityProviderMessage *store.IdentityProviderMessage) *IdentityProvider { + return &IdentityProvider{ ID: identityProviderMessage.ID, Name: identityProviderMessage.Name, - Type: api.IdentityProviderType(identityProviderMessage.Type), + Type: IdentityProviderType(identityProviderMessage.Type), IdentifierFilter: identityProviderMessage.IdentifierFilter, Config: convertIdentityProviderConfigFromStore(identityProviderMessage.Config), } } -func convertIdentityProviderConfigFromStore(config *store.IdentityProviderConfig) *api.IdentityProviderConfig { - return &api.IdentityProviderConfig{ - OAuth2Config: &api.IdentityProviderOAuth2Config{ +func convertIdentityProviderConfigFromStore(config *store.IdentityProviderConfig) *IdentityProviderConfig { + return &IdentityProviderConfig{ + OAuth2Config: &IdentityProviderOAuth2Config{ ClientID: config.OAuth2Config.ClientID, ClientSecret: config.OAuth2Config.ClientSecret, AuthURL: config.OAuth2Config.AuthURL, TokenURL: config.OAuth2Config.TokenURL, UserInfoURL: config.OAuth2Config.UserInfoURL, Scopes: config.OAuth2Config.Scopes, - FieldMapping: &api.FieldMapping{ + FieldMapping: &FieldMapping{ Identifier: config.OAuth2Config.FieldMapping.Identifier, DisplayName: config.OAuth2Config.FieldMapping.DisplayName, Email: config.OAuth2Config.FieldMapping.Email, @@ -213,7 +260,7 @@ func convertIdentityProviderConfigFromStore(config *store.IdentityProviderConfig } } -func convertIdentityProviderConfigToStore(config *api.IdentityProviderConfig) *store.IdentityProviderConfig { +func convertIdentityProviderConfigToStore(config *IdentityProviderConfig) *store.IdentityProviderConfig { return &store.IdentityProviderConfig{ OAuth2Config: &store.IdentityProviderOAuth2Config{ ClientID: config.OAuth2Config.ClientID, diff --git a/api/v1/user.go b/api/v1/user.go new file mode 100644 index 00000000..86472f00 --- /dev/null +++ b/api/v1/user.go @@ -0,0 +1,25 @@ +package v1 + +// Role is the type of a role. +type Role string + +const ( + // Host is the HOST role. + Host Role = "HOST" + // Admin is the ADMIN role. + Admin Role = "ADMIN" + // NormalUser is the USER role. + NormalUser Role = "USER" +) + +func (e Role) String() string { + switch e { + case Host: + return "HOST" + case Admin: + return "ADMIN" + case NormalUser: + return "USER" + } + return "USER" +} diff --git a/api/v1/v1.go b/api/v1/v1.go index 023f52be..9287c327 100644 --- a/api/v1/v1.go +++ b/api/v1/v1.go @@ -6,6 +6,17 @@ import ( "github.com/usememos/memos/store" ) +const ( + // Context section + // The key name used to store user id in the context + // user id is extracted from the jwt token subject field. + userIDContextKey = "user-id" +) + +func getUserIDContextKey() string { + return userIDContextKey +} + type APIV1Service struct { Secret string Profile *profile.Profile @@ -20,8 +31,8 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store } } -func (s *APIV1Service) Register(e *echo.Echo) { - apiV1Group := e.Group("/api/v1") +func (s *APIV1Service) Register(apiV1Group *echo.Group) { s.registerTestRoutes(apiV1Group) s.registerAuthRoutes(apiV1Group, s.Secret) + s.registerIdentityProviderRoutes(apiV1Group) } diff --git a/server/jwt.go b/server/jwt.go index c16e3d14..7bfd3c8b 100644 --- a/server/jwt.go +++ b/server/jwt.go @@ -22,6 +22,10 @@ const ( userIDContextKey = "user-id" ) +func getUserIDContextKey() string { + return userIDContextKey +} + // Claims creates a struct that will be encoded to a JWT. // We add jwt.RegisteredClaims as an embedded type, to provide fields such as name. type Claims struct { @@ -29,10 +33,6 @@ type Claims struct { jwt.RegisteredClaims } -func getUserIDContextKey() string { - return userIDContextKey -} - func extractTokenFromHeader(c echo.Context) (string, error) { authHeader := c.Request().Header.Get("Authorization") if authHeader == "" { @@ -82,7 +82,7 @@ func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.Ha } // Skip validation for server status endpoints. - if common.HasPrefixes(path, "/api/ping", "/api/idp", "/api/user/:id") && method == http.MethodGet { + if common.HasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet { return next(c) } @@ -111,15 +111,9 @@ func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.Ha } return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"]) }) - if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) { - return echo.NewHTTPError(http.StatusUnauthorized, - fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q. you may send request to the wrong environment", - claims.Audience, - auth.AccessTokenAudienceName, - )) + return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName)) } - generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration if err != nil { var ve *jwt.ValidationError @@ -130,11 +124,7 @@ func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.Ha generateToken = true } } else { - return &echo.HTTPError{ - Code: http.StatusUnauthorized, - Message: "Invalid or expired access token", - Internal: err, - } + return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token")) } } diff --git a/server/server.go b/server/server.go index 4030fc4d..15facd4b 100644 --- a/server/server.go +++ b/server/server.go @@ -105,12 +105,15 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store s.registerResourceRoutes(apiGroup) s.registerTagRoutes(apiGroup) s.registerStorageRoutes(apiGroup) - s.registerIdentityProviderRoutes(apiGroup) s.registerOpenAIRoutes(apiGroup) s.registerMemoRelationRoutes(apiGroup) + apiV1Group := e.Group("/api/v1") + apiV1Group.Use(func(next echo.HandlerFunc) echo.HandlerFunc { + return JWTMiddleware(s, next, s.Secret) + }) apiV1Service := apiV1.NewAPIV1Service(s.Secret, profile, store) - apiV1Service.Register(e) + apiV1Service.Register(apiV1Group) return s, nil } diff --git a/store/user.go b/store/user.go index b7e39128..00bd604e 100644 --- a/store/user.go +++ b/store/user.go @@ -27,6 +27,211 @@ func (s *Store) SeedDataForNewUser(ctx context.Context, user *api.User) error { return err } +// Role is the type of a role. +type Role string + +const ( + // Host is the HOST role. + Host Role = "HOST" + // Admin is the ADMIN role. + Admin Role = "ADMIN" + // NormalUser is the USER role. + NormalUser Role = "USER" +) + +func (e Role) String() string { + switch e { + case Host: + return "HOST" + case Admin: + return "ADMIN" + case NormalUser: + return "USER" + } + return "USER" +} + +type UserMessage struct { + ID int + + // Standard fields + RowStatus RowStatus + CreatedTs int64 + UpdatedTs int64 + + // Domain specific fields + Username string + Role Role + Email string + Nickname string + PasswordHash string + OpenID string + AvatarURL string +} + +type FindUserMessage struct { + ID *int + + // Standard fields + RowStatus *RowStatus + + // Domain specific fields + Username *string + Role *Role + Email *string + Nickname *string + OpenID *string +} + +func (s *Store) CreateUserV1(ctx context.Context, create *UserMessage) (*UserMessage, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, FormatError(err) + } + defer tx.Rollback() + + query := ` + INSERT INTO user ( + username, + role, + email, + nickname, + password_hash, + open_id + ) + VALUES (?, ?, ?, ?, ?, ?) + RETURNING id, avatar_url, created_ts, updated_ts, row_status + ` + if err := tx.QueryRowContext(ctx, query, + create.Username, + create.Role, + create.Email, + create.Nickname, + create.PasswordHash, + create.OpenID, + ).Scan( + &create.ID, + &create.AvatarURL, + &create.CreatedTs, + &create.UpdatedTs, + &create.RowStatus, + ); err != nil { + return nil, FormatError(err) + } + if err := tx.Commit(); err != nil { + return nil, FormatError(err) + } + userMessage := create + return userMessage, nil +} + +func (s *Store) ListUsers(ctx context.Context, find *FindUserMessage) ([]*UserMessage, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, FormatError(err) + } + defer tx.Rollback() + + list, err := listUsers(ctx, tx, find) + if err != nil { + return nil, err + } + + return list, nil +} + +func (s *Store) GetUser(ctx context.Context, find *FindUserMessage) (*UserMessage, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, FormatError(err) + } + defer tx.Rollback() + + list, err := listUsers(ctx, tx, find) + if err != nil { + return nil, err + } + if len(list) == 0 { + return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("user not found")} + } + + memoMessage := list[0] + return memoMessage, nil +} + +func listUsers(ctx context.Context, tx *sql.Tx, find *FindUserMessage) ([]*UserMessage, error) { + where, args := []string{"1 = 1"}, []any{} + + if v := find.ID; v != nil { + where, args = append(where, "id = ?"), append(args, *v) + } + if v := find.Username; v != nil { + where, args = append(where, "username = ?"), append(args, *v) + } + if v := find.Role; v != nil { + where, args = append(where, "role = ?"), append(args, *v) + } + if v := find.Email; v != nil { + where, args = append(where, "email = ?"), append(args, *v) + } + if v := find.Nickname; v != nil { + where, args = append(where, "nickname = ?"), append(args, *v) + } + if v := find.OpenID; v != nil { + where, args = append(where, "open_id = ?"), append(args, *v) + } + + query := ` + SELECT + id, + username, + role, + email, + nickname, + password_hash, + open_id, + avatar_url, + created_ts, + updated_ts, + row_status + FROM user + WHERE ` + strings.Join(where, " AND ") + ` + ORDER BY created_ts DESC, row_status DESC + ` + rows, err := tx.QueryContext(ctx, query, args...) + if err != nil { + return nil, FormatError(err) + } + defer rows.Close() + + userMessageList := make([]*UserMessage, 0) + for rows.Next() { + var userMessage UserMessage + if err := rows.Scan( + &userMessage.ID, + &userMessage.Username, + &userMessage.Role, + &userMessage.Email, + &userMessage.Nickname, + &userMessage.PasswordHash, + &userMessage.OpenID, + &userMessage.AvatarURL, + &userMessage.CreatedTs, + &userMessage.UpdatedTs, + &userMessage.RowStatus, + ); err != nil { + return nil, FormatError(err) + } + userMessageList = append(userMessageList, &userMessage) + } + + if err := rows.Err(); err != nil { + return nil, FormatError(err) + } + + return userMessageList, nil +} + // userRaw is the store model for an User. // Fields have exactly the same meanings as User. type userRaw struct { diff --git a/test/store/idp_test.go b/test/store/idp_test.go new file mode 100644 index 00000000..483c3060 --- /dev/null +++ b/test/store/idp_test.go @@ -0,0 +1,48 @@ +package teststore + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/usememos/memos/store" +) + +func TestIdentityProviderStore(t *testing.T) { + ctx := context.Background() + ts := NewTestingStore(ctx, t) + createdIDP, err := ts.CreateIdentityProvider(ctx, &store.IdentityProviderMessage{ + Name: "GitHub OAuth", + Type: store.IdentityProviderOAuth2, + IdentifierFilter: "", + Config: &store.IdentityProviderConfig{ + OAuth2Config: &store.IdentityProviderOAuth2Config{ + ClientID: "asd", + ClientSecret: "123", + AuthURL: "https://github.com/auth", + TokenURL: "https://github.com/token", + UserInfoURL: "https://github.com/user", + Scopes: []string{"login"}, + FieldMapping: &store.FieldMapping{ + Identifier: "login", + DisplayName: "name", + Email: "emai", + }, + }, + }, + }) + require.NoError(t, err) + idp, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProviderMessage{ + ID: &createdIDP.ID, + }) + require.NoError(t, err) + require.Equal(t, createdIDP, idp) + err = ts.DeleteIdentityProvider(ctx, &store.DeleteIdentityProviderMessage{ + ID: idp.ID, + }) + require.NoError(t, err) + idpList, err := ts.ListIdentityProviders(ctx, &store.FindIdentityProviderMessage{}) + require.NoError(t, err) + require.Equal(t, 0, len(idpList)) +} diff --git a/web/src/components/Settings/SSOSection.tsx b/web/src/components/Settings/SSOSection.tsx index d7bdccf0..9d51f59b 100644 --- a/web/src/components/Settings/SSOSection.tsx +++ b/web/src/components/Settings/SSOSection.tsx @@ -17,9 +17,7 @@ const SSOSection = () => { }, []); const fetchIdentityProviderList = async () => { - const { - data: { data: identityProviderList }, - } = await api.getIdentityProviderList(); + const { data: identityProviderList } = await api.getIdentityProviderList(); setIdentityProviderList(identityProviderList); }; diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts index 495af373..33267372 100644 --- a/web/src/helpers/api.ts +++ b/web/src/helpers/api.ts @@ -246,19 +246,19 @@ export function deleteStorage(storageId: StorageId) { } export function getIdentityProviderList() { - return axios.get>(`/api/idp`); + return axios.get(`/api/v1/idp`); } export function createIdentityProvider(identityProviderCreate: IdentityProviderCreate) { - return axios.post>(`/api/idp`, identityProviderCreate); + return axios.post(`/api/v1/idp`, identityProviderCreate); } export function patchIdentityProvider(identityProviderPatch: IdentityProviderPatch) { - return axios.patch>(`/api/idp/${identityProviderPatch.id}`, identityProviderPatch); + return axios.patch(`/api/v1/idp/${identityProviderPatch.id}`, identityProviderPatch); } export function deleteIdentityProvider(id: IdentityProviderId) { - return axios.delete(`/api/idp/${id}`); + return axios.delete(`/api/v1/idp/${id}`); } export async function getRepoStarCount() { diff --git a/web/src/pages/Auth.tsx b/web/src/pages/Auth.tsx index f1e6ae99..da63d78d 100644 --- a/web/src/pages/Auth.tsx +++ b/web/src/pages/Auth.tsx @@ -24,9 +24,7 @@ const Auth = () => { useEffect(() => { userStore.doSignOut().catch(); const fetchIdentityProviderList = async () => { - const { - data: { data: identityProviderList }, - } = await api.getIdentityProviderList(); + const { data: identityProviderList } = await api.getIdentityProviderList(); setIdentityProviderList(identityProviderList); }; fetchIdentityProviderList();