user_service.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. package v1
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "net/http"
  7. "regexp"
  8. "slices"
  9. "strings"
  10. "time"
  11. "github.com/golang-jwt/jwt/v5"
  12. "github.com/google/cel-go/cel"
  13. "github.com/labstack/echo/v4"
  14. "github.com/pkg/errors"
  15. "golang.org/x/crypto/bcrypt"
  16. expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
  17. "google.golang.org/genproto/googleapis/api/httpbody"
  18. "google.golang.org/grpc/codes"
  19. "google.golang.org/grpc/status"
  20. "google.golang.org/protobuf/types/known/emptypb"
  21. "google.golang.org/protobuf/types/known/timestamppb"
  22. "github.com/usememos/memos/internal/util"
  23. v1pb "github.com/usememos/memos/proto/gen/api/v1"
  24. storepb "github.com/usememos/memos/proto/gen/store"
  25. "github.com/usememos/memos/store"
  26. )
  27. func (s *APIV1Service) ListUsers(ctx context.Context, _ *v1pb.ListUsersRequest) (*v1pb.ListUsersResponse, error) {
  28. currentUser, err := s.GetCurrentUser(ctx)
  29. if err != nil {
  30. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  31. }
  32. if currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
  33. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  34. }
  35. users, err := s.Store.ListUsers(ctx, &store.FindUser{})
  36. if err != nil {
  37. return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
  38. }
  39. response := &v1pb.ListUsersResponse{
  40. Users: []*v1pb.User{},
  41. }
  42. for _, user := range users {
  43. response.Users = append(response.Users, convertUserFromStore(user))
  44. }
  45. return response, nil
  46. }
  47. func (s *APIV1Service) SearchUsers(ctx context.Context, request *v1pb.SearchUsersRequest) (*v1pb.SearchUsersResponse, error) {
  48. if request.Filter == "" {
  49. return nil, status.Errorf(codes.InvalidArgument, "filter is empty")
  50. }
  51. filter, err := parseSearchUsersFilter(request.Filter)
  52. if err != nil {
  53. return nil, status.Errorf(codes.InvalidArgument, "failed to parse filter: %v", err)
  54. }
  55. userFind := &store.FindUser{}
  56. if filter.Username != nil {
  57. userFind.Username = filter.Username
  58. }
  59. if filter.Random {
  60. userFind.Random = true
  61. }
  62. if filter.Limit != nil {
  63. userFind.Limit = filter.Limit
  64. }
  65. users, err := s.Store.ListUsers(ctx, userFind)
  66. if err != nil {
  67. return nil, status.Errorf(codes.Internal, "failed to search users: %v", err)
  68. }
  69. response := &v1pb.SearchUsersResponse{
  70. Users: []*v1pb.User{},
  71. }
  72. for _, user := range users {
  73. response.Users = append(response.Users, convertUserFromStore(user))
  74. }
  75. return response, nil
  76. }
  77. func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest) (*v1pb.User, error) {
  78. userID, err := ExtractUserIDFromName(request.Name)
  79. if err != nil {
  80. return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
  81. }
  82. user, err := s.Store.GetUser(ctx, &store.FindUser{
  83. ID: &userID,
  84. })
  85. if err != nil {
  86. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  87. }
  88. if user == nil {
  89. return nil, status.Errorf(codes.NotFound, "user not found")
  90. }
  91. return convertUserFromStore(user), nil
  92. }
  93. func (s *APIV1Service) GetUserAvatarBinary(ctx context.Context, request *v1pb.GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error) {
  94. userID, err := ExtractUserIDFromName(request.Name)
  95. if err != nil {
  96. return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
  97. }
  98. user, err := s.Store.GetUser(ctx, &store.FindUser{
  99. ID: &userID,
  100. })
  101. if err != nil {
  102. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  103. }
  104. if user == nil {
  105. return nil, status.Errorf(codes.NotFound, "user not found")
  106. }
  107. if user.AvatarURL == "" {
  108. return nil, status.Errorf(codes.NotFound, "avatar not found")
  109. }
  110. imageType, base64Data, err := extractImageInfo(user.AvatarURL)
  111. if err != nil {
  112. return nil, status.Errorf(codes.Internal, "failed to extract image info: %v", err)
  113. }
  114. imageData, err := base64.StdEncoding.DecodeString(base64Data)
  115. if err != nil {
  116. return nil, status.Errorf(codes.Internal, "failed to decode string: %v", err)
  117. }
  118. httpBody := &httpbody.HttpBody{
  119. ContentType: imageType,
  120. Data: imageData,
  121. }
  122. return httpBody, nil
  123. }
  124. func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserRequest) (*v1pb.User, error) {
  125. currentUser, err := s.GetCurrentUser(ctx)
  126. if err != nil {
  127. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  128. }
  129. if currentUser.Role != store.RoleHost {
  130. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  131. }
  132. if !util.UIDMatcher.MatchString(strings.ToLower(request.User.Username)) {
  133. return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username)
  134. }
  135. passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost)
  136. if err != nil {
  137. return nil, echo.NewHTTPError(http.StatusInternalServerError, "failed to generate password hash").SetInternal(err)
  138. }
  139. user, err := s.Store.CreateUser(ctx, &store.User{
  140. Username: request.User.Username,
  141. Role: convertUserRoleToStore(request.User.Role),
  142. Email: request.User.Email,
  143. Nickname: request.User.Nickname,
  144. PasswordHash: string(passwordHash),
  145. })
  146. if err != nil {
  147. return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
  148. }
  149. return convertUserFromStore(user), nil
  150. }
  151. func (s *APIV1Service) UpdateUser(ctx context.Context, request *v1pb.UpdateUserRequest) (*v1pb.User, error) {
  152. workspaceGeneralSetting, err := s.Store.GetWorkspaceGeneralSetting(ctx)
  153. if err != nil {
  154. return nil, status.Errorf(codes.Internal, "failed to get workspace general setting: %v", err)
  155. }
  156. userID, err := ExtractUserIDFromName(request.User.Name)
  157. if err != nil {
  158. return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
  159. }
  160. currentUser, err := s.GetCurrentUser(ctx)
  161. if err != nil {
  162. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  163. }
  164. if currentUser.ID != userID && currentUser.Role != store.RoleAdmin && currentUser.Role != store.RoleHost {
  165. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  166. }
  167. if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
  168. return nil, status.Errorf(codes.InvalidArgument, "update mask is empty")
  169. }
  170. user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &userID})
  171. if err != nil {
  172. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  173. }
  174. if user == nil {
  175. return nil, status.Errorf(codes.NotFound, "user not found")
  176. }
  177. currentTs := time.Now().Unix()
  178. update := &store.UpdateUser{
  179. ID: user.ID,
  180. UpdatedTs: &currentTs,
  181. }
  182. for _, field := range request.UpdateMask.Paths {
  183. if field == "username" {
  184. if workspaceGeneralSetting.DisallowChangeUsername {
  185. return nil, status.Errorf(codes.PermissionDenied, "permission denied: disallow change username")
  186. }
  187. if !util.UIDMatcher.MatchString(strings.ToLower(request.User.Username)) {
  188. return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username)
  189. }
  190. update.Username = &request.User.Username
  191. } else if field == "nickname" {
  192. if workspaceGeneralSetting.DisallowChangeNickname {
  193. return nil, status.Errorf(codes.PermissionDenied, "permission denied: disallow change nickname")
  194. }
  195. update.Nickname = &request.User.Nickname
  196. } else if field == "email" {
  197. update.Email = &request.User.Email
  198. } else if field == "avatar_url" {
  199. update.AvatarURL = &request.User.AvatarUrl
  200. } else if field == "description" {
  201. update.Description = &request.User.Description
  202. } else if field == "role" {
  203. role := convertUserRoleToStore(request.User.Role)
  204. update.Role = &role
  205. } else if field == "password" {
  206. passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost)
  207. if err != nil {
  208. return nil, echo.NewHTTPError(http.StatusInternalServerError, "failed to generate password hash").SetInternal(err)
  209. }
  210. passwordHashStr := string(passwordHash)
  211. update.PasswordHash = &passwordHashStr
  212. } else if field == "state" {
  213. rowStatus := convertStateToStore(request.User.State)
  214. update.RowStatus = &rowStatus
  215. } else {
  216. return nil, status.Errorf(codes.InvalidArgument, "invalid update path: %s", field)
  217. }
  218. }
  219. updatedUser, err := s.Store.UpdateUser(ctx, update)
  220. if err != nil {
  221. return nil, status.Errorf(codes.Internal, "failed to update user: %v", err)
  222. }
  223. return convertUserFromStore(updatedUser), nil
  224. }
  225. func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserRequest) (*emptypb.Empty, error) {
  226. userID, err := ExtractUserIDFromName(request.Name)
  227. if err != nil {
  228. return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
  229. }
  230. currentUser, err := s.GetCurrentUser(ctx)
  231. if err != nil {
  232. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  233. }
  234. if currentUser.ID != userID && currentUser.Role != store.RoleAdmin && currentUser.Role != store.RoleHost {
  235. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  236. }
  237. user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &userID})
  238. if err != nil {
  239. return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
  240. }
  241. if user == nil {
  242. return nil, status.Errorf(codes.NotFound, "user not found")
  243. }
  244. if err := s.Store.DeleteUser(ctx, &store.DeleteUser{
  245. ID: user.ID,
  246. }); err != nil {
  247. return nil, status.Errorf(codes.Internal, "failed to delete user: %v", err)
  248. }
  249. return &emptypb.Empty{}, nil
  250. }
  251. func getDefaultUserSetting(workspaceMemoRelatedSetting *storepb.WorkspaceMemoRelatedSetting) *v1pb.UserSetting {
  252. defaultVisibility := "PRIVATE"
  253. if workspaceMemoRelatedSetting.DefaultVisibility != "" {
  254. defaultVisibility = workspaceMemoRelatedSetting.DefaultVisibility
  255. }
  256. return &v1pb.UserSetting{
  257. Locale: "en",
  258. Appearance: "system",
  259. MemoVisibility: defaultVisibility,
  260. }
  261. }
  262. func (s *APIV1Service) GetUserSetting(ctx context.Context, _ *v1pb.GetUserSettingRequest) (*v1pb.UserSetting, error) {
  263. user, err := s.GetCurrentUser(ctx)
  264. if err != nil {
  265. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  266. }
  267. workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
  268. if err != nil {
  269. return nil, status.Errorf(codes.Internal, "failed to get workspace general setting: %v", err)
  270. }
  271. userSettings, err := s.Store.ListUserSettings(ctx, &store.FindUserSetting{
  272. UserID: &user.ID,
  273. })
  274. if err != nil {
  275. return nil, status.Errorf(codes.Internal, "failed to list user settings: %v", err)
  276. }
  277. // getDefaultUserSetting By workspaceSetting
  278. userSettingMessage := getDefaultUserSetting(workspaceMemoRelatedSetting)
  279. for _, setting := range userSettings {
  280. if setting.Key == storepb.UserSettingKey_LOCALE {
  281. userSettingMessage.Locale = setting.GetLocale()
  282. } else if setting.Key == storepb.UserSettingKey_APPEARANCE {
  283. userSettingMessage.Appearance = setting.GetAppearance()
  284. } else if setting.Key == storepb.UserSettingKey_MEMO_VISIBILITY {
  285. userSettingMessage.MemoVisibility = setting.GetMemoVisibility()
  286. }
  287. }
  288. return userSettingMessage, nil
  289. }
  290. func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.UpdateUserSettingRequest) (*v1pb.UserSetting, error) {
  291. user, err := s.GetCurrentUser(ctx)
  292. if err != nil {
  293. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  294. }
  295. if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
  296. return nil, status.Errorf(codes.InvalidArgument, "update mask is empty")
  297. }
  298. for _, field := range request.UpdateMask.Paths {
  299. if field == "locale" {
  300. if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
  301. UserId: user.ID,
  302. Key: storepb.UserSettingKey_LOCALE,
  303. Value: &storepb.UserSetting_Locale{
  304. Locale: request.Setting.Locale,
  305. },
  306. }); err != nil {
  307. return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
  308. }
  309. } else if field == "appearance" {
  310. if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
  311. UserId: user.ID,
  312. Key: storepb.UserSettingKey_APPEARANCE,
  313. Value: &storepb.UserSetting_Appearance{
  314. Appearance: request.Setting.Appearance,
  315. },
  316. }); err != nil {
  317. return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
  318. }
  319. } else if field == "memo_visibility" {
  320. if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
  321. UserId: user.ID,
  322. Key: storepb.UserSettingKey_MEMO_VISIBILITY,
  323. Value: &storepb.UserSetting_MemoVisibility{
  324. MemoVisibility: request.Setting.MemoVisibility,
  325. },
  326. }); err != nil {
  327. return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
  328. }
  329. } else {
  330. return nil, status.Errorf(codes.InvalidArgument, "invalid update path: %s", field)
  331. }
  332. }
  333. return s.GetUserSetting(ctx, &v1pb.GetUserSettingRequest{})
  334. }
  335. func (s *APIV1Service) ListUserAccessTokens(ctx context.Context, request *v1pb.ListUserAccessTokensRequest) (*v1pb.ListUserAccessTokensResponse, error) {
  336. userID, err := ExtractUserIDFromName(request.Name)
  337. if err != nil {
  338. return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
  339. }
  340. currentUser, err := s.GetCurrentUser(ctx)
  341. if err != nil {
  342. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  343. }
  344. if currentUser == nil {
  345. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  346. }
  347. if currentUser.ID != userID {
  348. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  349. }
  350. userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, userID)
  351. if err != nil {
  352. return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err)
  353. }
  354. accessTokens := []*v1pb.UserAccessToken{}
  355. for _, userAccessToken := range userAccessTokens {
  356. claims := &ClaimsMessage{}
  357. _, err := jwt.ParseWithClaims(userAccessToken.AccessToken, claims, func(t *jwt.Token) (any, error) {
  358. if t.Method.Alg() != jwt.SigningMethodHS256.Name {
  359. return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
  360. }
  361. if kid, ok := t.Header["kid"].(string); ok {
  362. if kid == "v1" {
  363. return []byte(s.Secret), nil
  364. }
  365. }
  366. return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
  367. })
  368. if err != nil {
  369. // If the access token is invalid or expired, just ignore it.
  370. continue
  371. }
  372. userAccessToken := &v1pb.UserAccessToken{
  373. AccessToken: userAccessToken.AccessToken,
  374. Description: userAccessToken.Description,
  375. IssuedAt: timestamppb.New(claims.IssuedAt.Time),
  376. }
  377. if claims.ExpiresAt != nil {
  378. userAccessToken.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time)
  379. }
  380. accessTokens = append(accessTokens, userAccessToken)
  381. }
  382. // Sort by issued time in descending order.
  383. slices.SortFunc(accessTokens, func(i, j *v1pb.UserAccessToken) int {
  384. return int(i.IssuedAt.Seconds - j.IssuedAt.Seconds)
  385. })
  386. response := &v1pb.ListUserAccessTokensResponse{
  387. AccessTokens: accessTokens,
  388. }
  389. return response, nil
  390. }
  391. func (s *APIV1Service) CreateUserAccessToken(ctx context.Context, request *v1pb.CreateUserAccessTokenRequest) (*v1pb.UserAccessToken, error) {
  392. userID, err := ExtractUserIDFromName(request.Name)
  393. if err != nil {
  394. return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
  395. }
  396. currentUser, err := s.GetCurrentUser(ctx)
  397. if err != nil {
  398. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  399. }
  400. if currentUser == nil {
  401. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  402. }
  403. if currentUser.ID != userID {
  404. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  405. }
  406. expiresAt := time.Time{}
  407. if request.ExpiresAt != nil {
  408. expiresAt = request.ExpiresAt.AsTime()
  409. }
  410. accessToken, err := GenerateAccessToken(currentUser.Username, currentUser.ID, expiresAt, []byte(s.Secret))
  411. if err != nil {
  412. return nil, status.Errorf(codes.Internal, "failed to generate access token: %v", err)
  413. }
  414. claims := &ClaimsMessage{}
  415. _, err = jwt.ParseWithClaims(accessToken, claims, func(t *jwt.Token) (any, error) {
  416. if t.Method.Alg() != jwt.SigningMethodHS256.Name {
  417. return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
  418. }
  419. if kid, ok := t.Header["kid"].(string); ok {
  420. if kid == "v1" {
  421. return []byte(s.Secret), nil
  422. }
  423. }
  424. return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
  425. })
  426. if err != nil {
  427. return nil, status.Errorf(codes.Internal, "failed to parse access token: %v", err)
  428. }
  429. // Upsert the access token to user setting store.
  430. if err := s.UpsertAccessTokenToStore(ctx, currentUser, accessToken, request.Description); err != nil {
  431. return nil, status.Errorf(codes.Internal, "failed to upsert access token to store: %v", err)
  432. }
  433. userAccessToken := &v1pb.UserAccessToken{
  434. AccessToken: accessToken,
  435. Description: request.Description,
  436. IssuedAt: timestamppb.New(claims.IssuedAt.Time),
  437. }
  438. if claims.ExpiresAt != nil {
  439. userAccessToken.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time)
  440. }
  441. return userAccessToken, nil
  442. }
  443. func (s *APIV1Service) DeleteUserAccessToken(ctx context.Context, request *v1pb.DeleteUserAccessTokenRequest) (*emptypb.Empty, error) {
  444. userID, err := ExtractUserIDFromName(request.Name)
  445. if err != nil {
  446. return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
  447. }
  448. currentUser, err := s.GetCurrentUser(ctx)
  449. if err != nil {
  450. return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
  451. }
  452. if currentUser == nil {
  453. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  454. }
  455. if currentUser.ID != userID {
  456. return nil, status.Errorf(codes.PermissionDenied, "permission denied")
  457. }
  458. userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, currentUser.ID)
  459. if err != nil {
  460. return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err)
  461. }
  462. updatedUserAccessTokens := []*storepb.AccessTokensUserSetting_AccessToken{}
  463. for _, userAccessToken := range userAccessTokens {
  464. if userAccessToken.AccessToken == request.AccessToken {
  465. continue
  466. }
  467. updatedUserAccessTokens = append(updatedUserAccessTokens, userAccessToken)
  468. }
  469. if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
  470. UserId: currentUser.ID,
  471. Key: storepb.UserSettingKey_ACCESS_TOKENS,
  472. Value: &storepb.UserSetting_AccessTokens{
  473. AccessTokens: &storepb.AccessTokensUserSetting{
  474. AccessTokens: updatedUserAccessTokens,
  475. },
  476. },
  477. }); err != nil {
  478. return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
  479. }
  480. return &emptypb.Empty{}, nil
  481. }
  482. func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store.User, accessToken, description string) error {
  483. userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID)
  484. if err != nil {
  485. return errors.Wrap(err, "failed to get user access tokens")
  486. }
  487. userAccessToken := storepb.AccessTokensUserSetting_AccessToken{
  488. AccessToken: accessToken,
  489. Description: description,
  490. }
  491. userAccessTokens = append(userAccessTokens, &userAccessToken)
  492. if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
  493. UserId: user.ID,
  494. Key: storepb.UserSettingKey_ACCESS_TOKENS,
  495. Value: &storepb.UserSetting_AccessTokens{
  496. AccessTokens: &storepb.AccessTokensUserSetting{
  497. AccessTokens: userAccessTokens,
  498. },
  499. },
  500. }); err != nil {
  501. return errors.Wrap(err, "failed to upsert user setting")
  502. }
  503. return nil
  504. }
  505. func convertUserFromStore(user *store.User) *v1pb.User {
  506. userpb := &v1pb.User{
  507. Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID),
  508. State: convertStateFromStore(user.RowStatus),
  509. CreateTime: timestamppb.New(time.Unix(user.CreatedTs, 0)),
  510. UpdateTime: timestamppb.New(time.Unix(user.UpdatedTs, 0)),
  511. Role: convertUserRoleFromStore(user.Role),
  512. Username: user.Username,
  513. Email: user.Email,
  514. Nickname: user.Nickname,
  515. AvatarUrl: user.AvatarURL,
  516. Description: user.Description,
  517. }
  518. // Use the avatar URL instead of raw base64 image data to reduce the response size.
  519. if user.AvatarURL != "" {
  520. userpb.AvatarUrl = fmt.Sprintf("/file/%s/avatar", userpb.Name)
  521. }
  522. return userpb
  523. }
  524. func convertUserRoleFromStore(role store.Role) v1pb.User_Role {
  525. switch role {
  526. case store.RoleHost:
  527. return v1pb.User_HOST
  528. case store.RoleAdmin:
  529. return v1pb.User_ADMIN
  530. case store.RoleUser:
  531. return v1pb.User_USER
  532. default:
  533. return v1pb.User_ROLE_UNSPECIFIED
  534. }
  535. }
  536. func convertUserRoleToStore(role v1pb.User_Role) store.Role {
  537. switch role {
  538. case v1pb.User_HOST:
  539. return store.RoleHost
  540. case v1pb.User_ADMIN:
  541. return store.RoleAdmin
  542. case v1pb.User_USER:
  543. return store.RoleUser
  544. default:
  545. return store.RoleUser
  546. }
  547. }
  548. // SearchUsersFilterCELAttributes are the CEL attributes for SearchUsersFilter.
  549. var SearchUsersFilterCELAttributes = []cel.EnvOption{
  550. cel.Variable("username", cel.StringType),
  551. cel.Variable("random", cel.BoolType),
  552. cel.Variable("limit", cel.IntType),
  553. }
  554. type SearchUsersFilter struct {
  555. Username *string
  556. Random bool
  557. Limit *int
  558. }
  559. func parseSearchUsersFilter(expression string) (*SearchUsersFilter, error) {
  560. e, err := cel.NewEnv(SearchUsersFilterCELAttributes...)
  561. if err != nil {
  562. return nil, err
  563. }
  564. ast, issues := e.Compile(expression)
  565. if issues != nil {
  566. return nil, errors.Errorf("found issue %v", issues)
  567. }
  568. filter := &SearchUsersFilter{}
  569. expr, err := cel.AstToParsedExpr(ast)
  570. if err != nil {
  571. return nil, err
  572. }
  573. callExpr := expr.GetExpr().GetCallExpr()
  574. findSearchUsersField(callExpr, filter)
  575. return filter, nil
  576. }
  577. func findSearchUsersField(callExpr *expr.Expr_Call, filter *SearchUsersFilter) {
  578. if len(callExpr.Args) == 2 {
  579. idExpr := callExpr.Args[0].GetIdentExpr()
  580. if idExpr != nil {
  581. if idExpr.Name == "username" {
  582. username := callExpr.Args[1].GetConstExpr().GetStringValue()
  583. filter.Username = &username
  584. } else if idExpr.Name == "random" {
  585. random := callExpr.Args[1].GetConstExpr().GetBoolValue()
  586. filter.Random = random
  587. } else if idExpr.Name == "limit" {
  588. limit := int(callExpr.Args[1].GetConstExpr().GetInt64Value())
  589. filter.Limit = &limit
  590. }
  591. return
  592. }
  593. }
  594. for _, arg := range callExpr.Args {
  595. callExpr := arg.GetCallExpr()
  596. if callExpr != nil {
  597. findSearchUsersField(callExpr, filter)
  598. }
  599. }
  600. }
  601. func extractImageInfo(dataURI string) (string, string, error) {
  602. dataURIRegex := regexp.MustCompile(`^data:(?P<type>.+);base64,(?P<base64>.+)`)
  603. matches := dataURIRegex.FindStringSubmatch(dataURI)
  604. if len(matches) != 3 {
  605. return "", "", errors.New("Invalid data URI format")
  606. }
  607. imageType := matches[1]
  608. base64Data := matches[2]
  609. return imageType, base64Data, nil
  610. }