@payloadcms/figma 0.0.1-alpha.45 → 0.0.1-alpha.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/api/figma-api.d.ts +20 -0
  2. package/dist/api/figma-api.d.ts.map +1 -1
  3. package/dist/api/figma-api.js +35 -1
  4. package/dist/api/figma-api.js.map +1 -1
  5. package/dist/commands/debug.js +1 -1
  6. package/dist/commands/debug.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +4 -4
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/db-content-api/index.js +1 -1
  11. package/dist/db-content-api/index.js.map +1 -1
  12. package/dist/db-content-api/utilities/meta/buildMeta.d.ts +2 -17
  13. package/dist/db-content-api/utilities/meta/buildMeta.d.ts.map +1 -1
  14. package/dist/db-content-api/utilities/meta/buildMeta.js +1 -4
  15. package/dist/db-content-api/utilities/meta/buildMeta.js.map +1 -1
  16. package/dist/db-content-api/utilities/meta/buildPathTypes.d.ts +2 -8
  17. package/dist/db-content-api/utilities/meta/buildPathTypes.d.ts.map +1 -1
  18. package/dist/db-content-api/utilities/meta/buildPathTypes.js +11 -41
  19. package/dist/db-content-api/utilities/meta/buildPathTypes.js.map +1 -1
  20. package/dist/db-content-api/utilities/meta/index.d.ts +5 -0
  21. package/dist/db-content-api/utilities/meta/index.d.ts.map +1 -0
  22. package/dist/db-content-api/utilities/meta/index.js +5 -0
  23. package/dist/db-content-api/utilities/meta/index.js.map +1 -0
  24. package/dist/endpoints/schema.d.ts +3 -0
  25. package/dist/endpoints/schema.d.ts.map +1 -0
  26. package/dist/endpoints/schema.js +29 -0
  27. package/dist/endpoints/schema.js.map +1 -0
  28. package/dist/exports/client.d.ts +2 -0
  29. package/dist/exports/client.d.ts.map +1 -1
  30. package/dist/exports/client.js +2 -0
  31. package/dist/exports/client.js.map +1 -1
  32. package/dist/oauth/components/FigmaAvatar/index.d.ts +4 -0
  33. package/dist/oauth/components/FigmaAvatar/index.d.ts.map +1 -0
  34. package/dist/oauth/components/FigmaAvatar/index.js +27 -0
  35. package/dist/oauth/components/FigmaAvatar/index.js.map +1 -0
  36. package/dist/oauth/components/FigmaAvatar/index.scss +12 -0
  37. package/dist/oauth/components/FigmaAvatarCell/index.d.ts +5 -0
  38. package/dist/oauth/components/FigmaAvatarCell/index.d.ts.map +1 -0
  39. package/dist/oauth/components/FigmaAvatarCell/index.js +25 -0
  40. package/dist/oauth/components/FigmaAvatarCell/index.js.map +1 -0
  41. package/dist/oauth/components/FigmaAvatarCell/index.scss +5 -0
  42. package/dist/oauth/components/FigmaAvatarPlaceholder/index.d.ts +9 -0
  43. package/dist/oauth/components/FigmaAvatarPlaceholder/index.d.ts.map +1 -0
  44. package/dist/oauth/components/FigmaAvatarPlaceholder/index.js +37 -0
  45. package/dist/oauth/components/FigmaAvatarPlaceholder/index.js.map +1 -0
  46. package/dist/oauth/components/FigmaAvatarPlaceholder/index.scss +28 -0
  47. package/dist/oauth/defaults.d.ts.map +1 -1
  48. package/dist/oauth/defaults.js +38 -5
  49. package/dist/oauth/defaults.js.map +1 -1
  50. package/dist/oauth/endpoints/getLoginEndpoint.d.ts.map +1 -1
  51. package/dist/oauth/endpoints/getLoginEndpoint.js +31 -1
  52. package/dist/oauth/endpoints/getLoginEndpoint.js.map +1 -1
  53. package/dist/plugin/build-config.d.ts +3 -3
  54. package/dist/plugin/build-config.d.ts.map +1 -1
  55. package/dist/plugin/build-config.js +48 -4
  56. package/dist/plugin/build-config.js.map +1 -1
  57. package/dist/templates/.env.example +1 -1
  58. package/dist/utils/payload-config-ast.js +5 -5
  59. package/dist/utils/payload-config-ast.js.map +1 -1
  60. package/dist/utils/project.js +1 -1
  61. package/dist/utils/project.js.map +1 -1
  62. package/package.json +1 -1
  63. package/dist/db-content-api/temp-utilities/types.d.ts +0 -22
  64. package/dist/db-content-api/temp-utilities/types.d.ts.map +0 -1
  65. package/dist/db-content-api/temp-utilities/types.js +0 -15
  66. package/dist/db-content-api/temp-utilities/types.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/oauth/components/FigmaAvatar/index.tsx"],"sourcesContent":["'use client'\nimport type React from 'react'\n\nimport { useAuth } from '@payloadcms/ui'\nimport { useState } from 'react'\n\nimport { FigmaAvatarPlaceholder, isValidImageUrl } from '../FigmaAvatarPlaceholder/index.js'\nimport './index.scss'\n\nconst baseClass = 'figma-avatar'\n\ntype UserWithFigmaFields = {\n figmaImageUrl?: string\n}\n\nexport const FigmaAvatar: React.FC = () => {\n const { user } = useAuth()\n const [hasError, setHasError] = useState(false)\n\n const figmaImageUrl = (user as UserWithFigmaFields)?.figmaImageUrl\n\n if (!figmaImageUrl || hasError || !isValidImageUrl(figmaImageUrl)) {\n return <FigmaAvatarPlaceholder size={25} />\n }\n\n return (\n <img\n alt=\"Profile\"\n className={`${baseClass}__image`}\n height={25}\n onError={() => setHasError(true)}\n src={figmaImageUrl}\n width={25}\n />\n )\n}\n"],"names":["useAuth","useState","FigmaAvatarPlaceholder","isValidImageUrl","baseClass","FigmaAvatar","user","hasError","setHasError","figmaImageUrl","size","img","alt","className","height","onError","src","width"],"mappings":"AAAA;;AAGA,SAASA,OAAO,QAAQ,iBAAgB;AACxC,SAASC,QAAQ,QAAQ,QAAO;AAEhC,SAASC,sBAAsB,EAAEC,eAAe,QAAQ,qCAAoC;AAC5F,OAAO,eAAc;AAErB,MAAMC,YAAY;AAMlB,OAAO,MAAMC,cAAwB;IACnC,MAAM,EAAEC,IAAI,EAAE,GAAGN;IACjB,MAAM,CAACO,UAAUC,YAAY,GAAGP,SAAS;IAEzC,MAAMQ,gBAAiBH,MAA8BG;IAErD,IAAI,CAACA,iBAAiBF,YAAY,CAACJ,gBAAgBM,gBAAgB;QACjE,qBAAO,KAACP;YAAuBQ,MAAM;;IACvC;IAEA,qBACE,KAACC;QACCC,KAAI;QACJC,WAAW,GAAGT,UAAU,OAAO,CAAC;QAChCU,QAAQ;QACRC,SAAS,IAAMP,YAAY;QAC3BQ,KAAKP;QACLQ,OAAO;;AAGb,EAAC"}
@@ -0,0 +1,12 @@
1
+ @layer payload-default {
2
+ .figma-avatar {
3
+ &__image {
4
+ border-radius: 50%;
5
+ border: 1px solid var(--theme-elevation-200);
6
+
7
+ &:hover {
8
+ border-color: var(--theme-elevation-600);
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,5 @@
1
+ import type { DefaultCellComponentProps } from 'payload';
2
+ import type React from 'react';
3
+ import './index.scss';
4
+ export declare const FigmaAvatarCell: React.FC<DefaultCellComponentProps>;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/oauth/components/FigmaAvatarCell/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,cAAc,CAAA;AAIrB,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAkB/D,CAAA"}
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { FigmaAvatarPlaceholder, isValidImageUrl } from '../FigmaAvatarPlaceholder/index.js';
5
+ import './index.scss';
6
+ const baseClass = 'figma-avatar-cell';
7
+ export const FigmaAvatarCell = ({ cellData })=>{
8
+ const [hasError, setHasError] = useState(false);
9
+ const figmaImageUrl = cellData;
10
+ if (!figmaImageUrl || hasError || !isValidImageUrl(figmaImageUrl)) {
11
+ return /*#__PURE__*/ _jsx(FigmaAvatarPlaceholder, {
12
+ size: 20
13
+ });
14
+ }
15
+ return /*#__PURE__*/ _jsx("img", {
16
+ alt: "Avatar",
17
+ className: `${baseClass}__image`,
18
+ height: 20,
19
+ onError: ()=>setHasError(true),
20
+ src: figmaImageUrl,
21
+ width: 20
22
+ });
23
+ };
24
+
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/oauth/components/FigmaAvatarCell/index.tsx"],"sourcesContent":["'use client'\nimport type { DefaultCellComponentProps } from 'payload'\nimport type React from 'react'\n\nimport { useState } from 'react'\n\nimport { FigmaAvatarPlaceholder, isValidImageUrl } from '../FigmaAvatarPlaceholder/index.js'\nimport './index.scss'\n\nconst baseClass = 'figma-avatar-cell'\n\nexport const FigmaAvatarCell: React.FC<DefaultCellComponentProps> = ({ cellData }) => {\n const [hasError, setHasError] = useState(false)\n const figmaImageUrl = cellData as string | undefined\n\n if (!figmaImageUrl || hasError || !isValidImageUrl(figmaImageUrl)) {\n return <FigmaAvatarPlaceholder size={20} />\n }\n\n return (\n <img\n alt=\"Avatar\"\n className={`${baseClass}__image`}\n height={20}\n onError={() => setHasError(true)}\n src={figmaImageUrl}\n width={20}\n />\n )\n}\n"],"names":["useState","FigmaAvatarPlaceholder","isValidImageUrl","baseClass","FigmaAvatarCell","cellData","hasError","setHasError","figmaImageUrl","size","img","alt","className","height","onError","src","width"],"mappings":"AAAA;;AAIA,SAASA,QAAQ,QAAQ,QAAO;AAEhC,SAASC,sBAAsB,EAAEC,eAAe,QAAQ,qCAAoC;AAC5F,OAAO,eAAc;AAErB,MAAMC,YAAY;AAElB,OAAO,MAAMC,kBAAuD,CAAC,EAAEC,QAAQ,EAAE;IAC/E,MAAM,CAACC,UAAUC,YAAY,GAAGP,SAAS;IACzC,MAAMQ,gBAAgBH;IAEtB,IAAI,CAACG,iBAAiBF,YAAY,CAACJ,gBAAgBM,gBAAgB;QACjE,qBAAO,KAACP;YAAuBQ,MAAM;;IACvC;IAEA,qBACE,KAACC;QACCC,KAAI;QACJC,WAAW,GAAGT,UAAU,OAAO,CAAC;QAChCU,QAAQ;QACRC,SAAS,IAAMP,YAAY;QAC3BQ,KAAKP;QACLQ,OAAO;;AAGb,EAAC"}
@@ -0,0 +1,5 @@
1
+ .figma-avatar-cell {
2
+ &__image {
3
+ border-radius: 50%;
4
+ }
5
+ }
@@ -0,0 +1,9 @@
1
+ import type React from 'react';
2
+ import './index.scss';
3
+ type FigmaAvatarPlaceholderProps = {
4
+ size?: number;
5
+ };
6
+ export declare const FigmaAvatarPlaceholder: React.FC<FigmaAvatarPlaceholderProps>;
7
+ export declare function isValidImageUrl(url: string): boolean;
8
+ export {};
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/oauth/components/FigmaAvatarPlaceholder/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,cAAc,CAAA;AAIrB,KAAK,2BAA2B,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAMxE,CAAA;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOpD"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './index.scss';
3
+ const baseClass = 'figma-avatar-placeholder';
4
+ export const FigmaAvatarPlaceholder = ({ size = 25 })=>/*#__PURE__*/ _jsxs("svg", {
5
+ className: baseClass,
6
+ height: size,
7
+ viewBox: "0 0 25 25",
8
+ width: size,
9
+ children: [
10
+ /*#__PURE__*/ _jsx("circle", {
11
+ className: `${baseClass}__bg`,
12
+ cx: "12.5",
13
+ cy: "12.5",
14
+ r: "11.5"
15
+ }),
16
+ /*#__PURE__*/ _jsx("circle", {
17
+ className: `${baseClass}__head`,
18
+ cx: "12.5",
19
+ cy: "10",
20
+ r: "4"
21
+ }),
22
+ /*#__PURE__*/ _jsx("path", {
23
+ className: `${baseClass}__body`,
24
+ d: "M12.5 15c-4 0-7 2-7 4.5v1h14v-1c0-2.5-3-4.5-7-4.5z"
25
+ })
26
+ ]
27
+ });
28
+ export function isValidImageUrl(url) {
29
+ try {
30
+ const parsed = new URL(url);
31
+ return parsed.protocol === 'https:' || parsed.protocol === 'http:';
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/oauth/components/FigmaAvatarPlaceholder/index.tsx"],"sourcesContent":["import type React from 'react'\n\nimport './index.scss'\n\nconst baseClass = 'figma-avatar-placeholder'\n\ntype FigmaAvatarPlaceholderProps = {\n size?: number\n}\n\nexport const FigmaAvatarPlaceholder: React.FC<FigmaAvatarPlaceholderProps> = ({ size = 25 }) => (\n <svg className={baseClass} height={size} viewBox=\"0 0 25 25\" width={size}>\n <circle className={`${baseClass}__bg`} cx=\"12.5\" cy=\"12.5\" r=\"11.5\" />\n <circle className={`${baseClass}__head`} cx=\"12.5\" cy=\"10\" r=\"4\" />\n <path className={`${baseClass}__body`} d=\"M12.5 15c-4 0-7 2-7 4.5v1h14v-1c0-2.5-3-4.5-7-4.5z\" />\n </svg>\n)\n\nexport function isValidImageUrl(url: string): boolean {\n try {\n const parsed = new URL(url)\n return parsed.protocol === 'https:' || parsed.protocol === 'http:'\n } catch {\n return false\n }\n}\n"],"names":["baseClass","FigmaAvatarPlaceholder","size","svg","className","height","viewBox","width","circle","cx","cy","r","path","d","isValidImageUrl","url","parsed","URL","protocol"],"mappings":";AAEA,OAAO,eAAc;AAErB,MAAMA,YAAY;AAMlB,OAAO,MAAMC,yBAAgE,CAAC,EAAEC,OAAO,EAAE,EAAE,iBACzF,MAACC;QAAIC,WAAWJ;QAAWK,QAAQH;QAAMI,SAAQ;QAAYC,OAAOL;;0BAClE,KAACM;gBAAOJ,WAAW,GAAGJ,UAAU,IAAI,CAAC;gBAAES,IAAG;gBAAOC,IAAG;gBAAOC,GAAE;;0BAC7D,KAACH;gBAAOJ,WAAW,GAAGJ,UAAU,MAAM,CAAC;gBAAES,IAAG;gBAAOC,IAAG;gBAAKC,GAAE;;0BAC7D,KAACC;gBAAKR,WAAW,GAAGJ,UAAU,MAAM,CAAC;gBAAEa,GAAE;;;OAE5C;AAED,OAAO,SAASC,gBAAgBC,GAAW;IACzC,IAAI;QACF,MAAMC,SAAS,IAAIC,IAAIF;QACvB,OAAOC,OAAOE,QAAQ,KAAK,YAAYF,OAAOE,QAAQ,KAAK;IAC7D,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
@@ -0,0 +1,28 @@
1
+ @layer payload-default {
2
+ .figma-avatar-placeholder {
3
+ &__bg {
4
+ fill: var(--theme-elevation-50);
5
+ stroke: var(--theme-elevation-200);
6
+ stroke-width: 1px;
7
+ }
8
+
9
+ &__head,
10
+ &__body {
11
+ fill: var(--theme-elevation-200);
12
+ }
13
+
14
+ &:hover {
15
+ .figma-avatar-placeholder {
16
+ &__bg {
17
+ fill: var(--theme-elevation-200);
18
+ stroke: var(--theme-elevation-600);
19
+ }
20
+
21
+ &__head,
22
+ &__body {
23
+ fill: var(--theme-elevation-600);
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/oauth/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAQ,MAAM,SAAS,CAAA;AAI5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAKhD,eAAO,MAAM,YAAY,UAAiC,CAAA;AAE1D,eAAO,MAAM,oBAAoB,EAAE,SAQlC,CAAA;AAED,eAAO,MAAM,aAAa,iDAKrB;IACD,UAAU,EAAE,gBAAgB,CAAA;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,UAAU,GAAG,SAAS,CAAA;CACtC,KAAG,cAuGH,CAAA"}
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/oauth/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAa,MAAM,SAAS,CAAA;AAKjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA4BhD,eAAO,MAAM,YAAY,UAAiC,CAAA;AAE1D,eAAO,MAAM,oBAAoB,EAAE,SAQlC,CAAA;AAED,eAAO,MAAM,aAAa,iDAKrB;IACD,UAAU,EAAE,gBAAgB,CAAA;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,UAAU,GAAG,SAAS,CAAA;CACtC,KAAG,cAkIH,CAAA"}
@@ -1,6 +1,19 @@
1
+ import { parseCookies } from 'payload';
1
2
  import { v4 as uuid } from 'uuid';
2
3
  import { hasUserTokenPropsChanged } from './utilities/hasUserTokenPropsChanged.js';
3
4
  import { isDuplicateKeyError } from './utilities/isDuplicateKeyError.js';
5
+ function getFigmaUserInfo(headers) {
6
+ try {
7
+ const cookies = parseCookies(headers);
8
+ const encoded = cookies.get('figma-user-info');
9
+ if (!encoded) {
10
+ return null;
11
+ }
12
+ return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'));
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
4
17
  export const defaultScope = [
5
18
  'openid',
6
19
  'profile',
@@ -15,7 +28,7 @@ export const defaultUsernameField = {
15
28
  },
16
29
  unique: true
17
30
  };
18
- export const defaultVerify = ({ collection, strategyName, usernameField })=>async ({ payload, token })=>{
31
+ export const defaultVerify = ({ collection, strategyName, usernameField })=>async ({ headers, payload, token })=>{
19
32
  let tokenUsername = 'preferred_username';
20
33
  if (usernameField.name !== 'preferredUsername') {
21
34
  tokenUsername = usernameField.name;
@@ -25,6 +38,13 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
25
38
  user: null
26
39
  };
27
40
  }
41
+ const figmaUserInfo = getFigmaUserInfo(headers);
42
+ let responseHeaders;
43
+ // Clear the cookie after reading
44
+ if (figmaUserInfo) {
45
+ responseHeaders = new Headers();
46
+ responseHeaders.append('Set-Cookie', 'figma-user-info=; Max-Age=0; Path=/;');
47
+ }
28
48
  let user = null;
29
49
  const depth = typeof collection.auth === 'object' ? collection.auth.depth : undefined;
30
50
  try {
@@ -41,13 +61,19 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
41
61
  });
42
62
  if (results.docs.length > 0) {
43
63
  user = results.docs[0];
44
- // Update user with new token data
45
- if (hasUserTokenPropsChanged(user, token)) {
64
+ // Check if update needed (token changed OR figma info missing/changed)
65
+ const userWithFigma = user;
66
+ const needsFigmaUpdate = figmaUserInfo && (userWithFigma.figmaHandle !== figmaUserInfo.handle || userWithFigma.figmaImageUrl !== figmaUserInfo.img_url);
67
+ if (hasUserTokenPropsChanged(user, token) || needsFigmaUpdate) {
46
68
  user = await payload.update({
47
69
  id: user.id,
48
70
  collection: collection.slug,
49
71
  data: {
50
- ...token
72
+ ...token,
73
+ ...figmaUserInfo && {
74
+ figmaHandle: figmaUserInfo.handle,
75
+ figmaImageUrl: figmaUserInfo.img_url
76
+ }
51
77
  },
52
78
  depth
53
79
  });
@@ -56,7 +82,11 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
56
82
  const newUser = {
57
83
  email: token.preferredUsername || token.email,
58
84
  [usernameField.name]: token[tokenUsername],
59
- ...token
85
+ ...token,
86
+ ...figmaUserInfo && {
87
+ figmaHandle: figmaUserInfo.handle,
88
+ figmaImageUrl: figmaUserInfo.img_url
89
+ }
60
90
  };
61
91
  if (typeof collection.auth === 'object' && !collection.auth.disableLocalStrategy) {
62
92
  newUser.password = uuid();
@@ -96,6 +126,7 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
96
126
  msg: 'Error verifying user from defaultVerify.'
97
127
  });
98
128
  return {
129
+ responseHeaders,
99
130
  user: null
100
131
  };
101
132
  }
@@ -104,10 +135,12 @@ export const defaultVerify = ({ collection, strategyName, usernameField })=>asyn
104
135
  user._strategy = strategyName;
105
136
  user.exp = token.exp;
106
137
  return {
138
+ responseHeaders,
107
139
  user
108
140
  };
109
141
  }
110
142
  return {
143
+ responseHeaders,
111
144
  user: null
112
145
  };
113
146
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/oauth/defaults.ts"],"sourcesContent":["import type { CollectionConfig, EmailField, TextField, User } from 'payload'\n\nimport { v4 as uuid } from 'uuid'\n\nimport type { VerifyFunction } from './types.js'\n\nimport { hasUserTokenPropsChanged } from './utilities/hasUserTokenPropsChanged.js'\nimport { isDuplicateKeyError } from './utilities/isDuplicateKeyError.js'\n\nexport const defaultScope = ['openid', 'profile', 'email']\n\nexport const defaultUsernameField: TextField = {\n name: 'preferredUsername',\n type: 'text',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n unique: true,\n}\n\nexport const defaultVerify =\n ({\n collection,\n strategyName,\n usernameField,\n }: {\n collection: CollectionConfig\n strategyName: string\n usernameField: EmailField | TextField\n }): VerifyFunction =>\n async ({ payload, token }) => {\n let tokenUsername = 'preferred_username'\n\n if (usernameField.name !== 'preferredUsername') {\n tokenUsername = usernameField.name\n }\n\n if (!token[tokenUsername]) {\n return {\n user: null,\n }\n }\n\n let user: null | User = null\n const depth = typeof collection.auth === 'object' ? collection.auth.depth : undefined\n\n try {\n const results = await payload.find({\n collection: collection.slug,\n depth,\n limit: 1,\n pagination: false,\n where: {\n [usernameField.name]: { equals: token[tokenUsername] },\n },\n })\n\n if (results.docs.length > 0) {\n user = results.docs[0] as User\n // Update user with new token data\n if (hasUserTokenPropsChanged(user, token)) {\n user = (await payload.update({\n id: user.id,\n collection: collection.slug,\n data: {\n ...token,\n },\n depth,\n })) as User\n }\n } else {\n const newUser = {\n email: token.preferredUsername || token.email,\n [usernameField.name]: token[tokenUsername],\n ...token,\n }\n\n if (typeof collection.auth === 'object' && !collection.auth.disableLocalStrategy) {\n newUser.password = uuid()\n }\n\n try {\n user = (await payload.create({\n collection: collection.slug,\n data: newUser,\n depth,\n overrideAccess: true,\n })) as User\n } catch (createErr: unknown) {\n // Race condition: if unique constraint violation, user was created by concurrent request\n if (isDuplicateKeyError(createErr)) {\n const retryResults = await payload.find({\n collection: collection.slug,\n depth,\n limit: 1,\n pagination: false,\n where: {\n [usernameField.name]: { equals: token[tokenUsername] },\n },\n })\n\n if (retryResults.docs.length > 0) {\n user = retryResults.docs[0] as User\n }\n } else {\n throw createErr\n }\n }\n }\n } catch (err: unknown) {\n payload.logger.error({\n err,\n msg: 'Error verifying user from defaultVerify.',\n })\n return {\n user: null,\n }\n }\n\n if (user) {\n user.collection = collection.slug\n user._strategy = strategyName\n user.exp = token.exp\n\n return {\n user,\n }\n }\n\n return {\n user: null,\n }\n }\n"],"names":["v4","uuid","hasUserTokenPropsChanged","isDuplicateKeyError","defaultScope","defaultUsernameField","name","type","admin","position","readOnly","unique","defaultVerify","collection","strategyName","usernameField","payload","token","tokenUsername","user","depth","auth","undefined","results","find","slug","limit","pagination","where","equals","docs","length","update","id","data","newUser","email","preferredUsername","disableLocalStrategy","password","create","overrideAccess","createErr","retryResults","err","logger","error","msg","_strategy","exp"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/oauth/defaults.ts"],"sourcesContent":["import type { CollectionConfig, EmailField, TextField, TypedUser } from 'payload'\n\nimport { parseCookies } from 'payload'\nimport { v4 as uuid } from 'uuid'\n\nimport type { VerifyFunction } from './types.js'\n\nimport { hasUserTokenPropsChanged } from './utilities/hasUserTokenPropsChanged.js'\nimport { isDuplicateKeyError } from './utilities/isDuplicateKeyError.js'\n\ntype FigmaUserInfoCookie = {\n handle: string\n img_url: string\n}\n\ntype UserWithFigmaFields = {\n figmaHandle?: string\n figmaImageUrl?: string\n} & TypedUser\n\nfunction getFigmaUserInfo(headers: Headers): FigmaUserInfoCookie | null {\n try {\n const cookies = parseCookies(headers)\n const encoded = cookies.get('figma-user-info')\n if (!encoded) {\n return null\n }\n return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'))\n } catch {\n return null\n }\n}\n\nexport const defaultScope = ['openid', 'profile', 'email']\n\nexport const defaultUsernameField: TextField = {\n name: 'preferredUsername',\n type: 'text',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n unique: true,\n}\n\nexport const defaultVerify =\n ({\n collection,\n strategyName,\n usernameField,\n }: {\n collection: CollectionConfig\n strategyName: string\n usernameField: EmailField | TextField\n }): VerifyFunction =>\n async ({ headers, payload, token }) => {\n let tokenUsername = 'preferred_username'\n\n if (usernameField.name !== 'preferredUsername') {\n tokenUsername = usernameField.name\n }\n\n if (!token[tokenUsername]) {\n return {\n user: null,\n }\n }\n\n const figmaUserInfo = getFigmaUserInfo(headers)\n let responseHeaders: Headers | undefined\n\n // Clear the cookie after reading\n if (figmaUserInfo) {\n responseHeaders = new Headers()\n responseHeaders.append('Set-Cookie', 'figma-user-info=; Max-Age=0; Path=/;')\n }\n\n let user: null | TypedUser | UserWithFigmaFields = null\n const depth = typeof collection.auth === 'object' ? collection.auth.depth : undefined\n\n try {\n const results = await payload.find({\n collection: collection.slug,\n depth,\n limit: 1,\n pagination: false,\n where: {\n [usernameField.name]: { equals: token[tokenUsername] },\n },\n })\n\n if (results.docs.length > 0) {\n user = results.docs[0] as UserWithFigmaFields\n\n // Check if update needed (token changed OR figma info missing/changed)\n const userWithFigma = user\n const needsFigmaUpdate =\n figmaUserInfo &&\n (userWithFigma.figmaHandle !== figmaUserInfo.handle ||\n userWithFigma.figmaImageUrl !== figmaUserInfo.img_url)\n\n if (hasUserTokenPropsChanged(user, token) || needsFigmaUpdate) {\n user = (await payload.update({\n id: user.id,\n collection: collection.slug,\n data: {\n ...token,\n ...(figmaUserInfo && {\n figmaHandle: figmaUserInfo.handle,\n figmaImageUrl: figmaUserInfo.img_url,\n }),\n },\n depth,\n })) as TypedUser\n }\n } else {\n const newUser: Record<string, unknown> = {\n email: token.preferredUsername || token.email,\n [usernameField.name]: token[tokenUsername],\n ...token,\n ...(figmaUserInfo && {\n figmaHandle: figmaUserInfo.handle,\n figmaImageUrl: figmaUserInfo.img_url,\n }),\n }\n\n if (typeof collection.auth === 'object' && !collection.auth.disableLocalStrategy) {\n newUser.password = uuid()\n }\n\n try {\n user = (await payload.create({\n collection: collection.slug,\n data: newUser,\n depth,\n overrideAccess: true,\n })) as TypedUser\n } catch (createErr: unknown) {\n // Race condition: if unique constraint violation, user was created by concurrent request\n if (isDuplicateKeyError(createErr)) {\n const retryResults = await payload.find({\n collection: collection.slug,\n depth,\n limit: 1,\n pagination: false,\n where: {\n [usernameField.name]: { equals: token[tokenUsername] },\n },\n })\n\n if (retryResults.docs.length > 0) {\n user = retryResults.docs[0] as TypedUser\n }\n } else {\n throw createErr\n }\n }\n }\n } catch (err: unknown) {\n payload.logger.error({\n err,\n msg: 'Error verifying user from defaultVerify.',\n })\n return {\n responseHeaders,\n user: null,\n }\n }\n\n if (user) {\n user.collection = collection.slug\n user._strategy = strategyName\n user.exp = token.exp\n\n return {\n responseHeaders,\n user,\n }\n }\n\n return {\n responseHeaders,\n user: null,\n }\n }\n"],"names":["parseCookies","v4","uuid","hasUserTokenPropsChanged","isDuplicateKeyError","getFigmaUserInfo","headers","cookies","encoded","get","JSON","parse","Buffer","from","toString","defaultScope","defaultUsernameField","name","type","admin","position","readOnly","unique","defaultVerify","collection","strategyName","usernameField","payload","token","tokenUsername","user","figmaUserInfo","responseHeaders","Headers","append","depth","auth","undefined","results","find","slug","limit","pagination","where","equals","docs","length","userWithFigma","needsFigmaUpdate","figmaHandle","handle","figmaImageUrl","img_url","update","id","data","newUser","email","preferredUsername","disableLocalStrategy","password","create","overrideAccess","createErr","retryResults","err","logger","error","msg","_strategy","exp"],"mappings":"AAEA,SAASA,YAAY,QAAQ,UAAS;AACtC,SAASC,MAAMC,IAAI,QAAQ,OAAM;AAIjC,SAASC,wBAAwB,QAAQ,0CAAyC;AAClF,SAASC,mBAAmB,QAAQ,qCAAoC;AAYxE,SAASC,iBAAiBC,OAAgB;IACxC,IAAI;QACF,MAAMC,UAAUP,aAAaM;QAC7B,MAAME,UAAUD,QAAQE,GAAG,CAAC;QAC5B,IAAI,CAACD,SAAS;YACZ,OAAO;QACT;QACA,OAAOE,KAAKC,KAAK,CAACC,OAAOC,IAAI,CAACL,SAAS,UAAUM,QAAQ,CAAC;IAC5D,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,MAAMC,eAAe;IAAC;IAAU;IAAW;CAAQ,CAAA;AAE1D,OAAO,MAAMC,uBAAkC;IAC7CC,MAAM;IACNC,MAAM;IACNC,OAAO;QACLC,UAAU;QACVC,UAAU;IACZ;IACAC,QAAQ;AACV,EAAC;AAED,OAAO,MAAMC,gBACX,CAAC,EACCC,UAAU,EACVC,YAAY,EACZC,aAAa,EAKd,GACD,OAAO,EAAEpB,OAAO,EAAEqB,OAAO,EAAEC,KAAK,EAAE;QAChC,IAAIC,gBAAgB;QAEpB,IAAIH,cAAcT,IAAI,KAAK,qBAAqB;YAC9CY,gBAAgBH,cAAcT,IAAI;QACpC;QAEA,IAAI,CAACW,KAAK,CAACC,cAAc,EAAE;YACzB,OAAO;gBACLC,MAAM;YACR;QACF;QAEA,MAAMC,gBAAgB1B,iBAAiBC;QACvC,IAAI0B;QAEJ,iCAAiC;QACjC,IAAID,eAAe;YACjBC,kBAAkB,IAAIC;YACtBD,gBAAgBE,MAAM,CAAC,cAAc;QACvC;QAEA,IAAIJ,OAA+C;QACnD,MAAMK,QAAQ,OAAOX,WAAWY,IAAI,KAAK,WAAWZ,WAAWY,IAAI,CAACD,KAAK,GAAGE;QAE5E,IAAI;YACF,MAAMC,UAAU,MAAMX,QAAQY,IAAI,CAAC;gBACjCf,YAAYA,WAAWgB,IAAI;gBAC3BL;gBACAM,OAAO;gBACPC,YAAY;gBACZC,OAAO;oBACL,CAACjB,cAAcT,IAAI,CAAC,EAAE;wBAAE2B,QAAQhB,KAAK,CAACC,cAAc;oBAAC;gBACvD;YACF;YAEA,IAAIS,QAAQO,IAAI,CAACC,MAAM,GAAG,GAAG;gBAC3BhB,OAAOQ,QAAQO,IAAI,CAAC,EAAE;gBAEtB,uEAAuE;gBACvE,MAAME,gBAAgBjB;gBACtB,MAAMkB,mBACJjB,iBACCgB,CAAAA,cAAcE,WAAW,KAAKlB,cAAcmB,MAAM,IACjDH,cAAcI,aAAa,KAAKpB,cAAcqB,OAAO,AAAD;gBAExD,IAAIjD,yBAAyB2B,MAAMF,UAAUoB,kBAAkB;oBAC7DlB,OAAQ,MAAMH,QAAQ0B,MAAM,CAAC;wBAC3BC,IAAIxB,KAAKwB,EAAE;wBACX9B,YAAYA,WAAWgB,IAAI;wBAC3Be,MAAM;4BACJ,GAAG3B,KAAK;4BACR,GAAIG,iBAAiB;gCACnBkB,aAAalB,cAAcmB,MAAM;gCACjCC,eAAepB,cAAcqB,OAAO;4BACtC,CAAC;wBACH;wBACAjB;oBACF;gBACF;YACF,OAAO;gBACL,MAAMqB,UAAmC;oBACvCC,OAAO7B,MAAM8B,iBAAiB,IAAI9B,MAAM6B,KAAK;oBAC7C,CAAC/B,cAAcT,IAAI,CAAC,EAAEW,KAAK,CAACC,cAAc;oBAC1C,GAAGD,KAAK;oBACR,GAAIG,iBAAiB;wBACnBkB,aAAalB,cAAcmB,MAAM;wBACjCC,eAAepB,cAAcqB,OAAO;oBACtC,CAAC;gBACH;gBAEA,IAAI,OAAO5B,WAAWY,IAAI,KAAK,YAAY,CAACZ,WAAWY,IAAI,CAACuB,oBAAoB,EAAE;oBAChFH,QAAQI,QAAQ,GAAG1D;gBACrB;gBAEA,IAAI;oBACF4B,OAAQ,MAAMH,QAAQkC,MAAM,CAAC;wBAC3BrC,YAAYA,WAAWgB,IAAI;wBAC3Be,MAAMC;wBACNrB;wBACA2B,gBAAgB;oBAClB;gBACF,EAAE,OAAOC,WAAoB;oBAC3B,yFAAyF;oBACzF,IAAI3D,oBAAoB2D,YAAY;wBAClC,MAAMC,eAAe,MAAMrC,QAAQY,IAAI,CAAC;4BACtCf,YAAYA,WAAWgB,IAAI;4BAC3BL;4BACAM,OAAO;4BACPC,YAAY;4BACZC,OAAO;gCACL,CAACjB,cAAcT,IAAI,CAAC,EAAE;oCAAE2B,QAAQhB,KAAK,CAACC,cAAc;gCAAC;4BACvD;wBACF;wBAEA,IAAImC,aAAanB,IAAI,CAACC,MAAM,GAAG,GAAG;4BAChChB,OAAOkC,aAAanB,IAAI,CAAC,EAAE;wBAC7B;oBACF,OAAO;wBACL,MAAMkB;oBACR;gBACF;YACF;QACF,EAAE,OAAOE,KAAc;YACrBtC,QAAQuC,MAAM,CAACC,KAAK,CAAC;gBACnBF;gBACAG,KAAK;YACP;YACA,OAAO;gBACLpC;gBACAF,MAAM;YACR;QACF;QAEA,IAAIA,MAAM;YACRA,KAAKN,UAAU,GAAGA,WAAWgB,IAAI;YACjCV,KAAKuC,SAAS,GAAG5C;YACjBK,KAAKwC,GAAG,GAAG1C,MAAM0C,GAAG;YAEpB,OAAO;gBACLtC;gBACAF;YACF;QACF;QAEA,OAAO;YACLE;YACAF,MAAM;QACR;IACF,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"getLoginEndpoint.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAKzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAY,MAAM,aAAa,CAAA;AAQ7E,UAAU,IAAI;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,QAAQ,CAAA;CACnB;AAID,eAAO,MAAM,gBAAgB,8EAM1B,IAAI,KAAG,QA6PR,CAAA"}
1
+ {"version":3,"file":"getLoginEndpoint.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAMzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAY,MAAM,aAAa,CAAA;AAQ7E,UAAU,IAAI;IACZ,UAAU,EAAE,gBAAgB,CAAA;IAC5B,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,QAAQ,CAAA;CACnB;AAID,eAAO,MAAM,gBAAgB,8EAM1B,IAAI,KAAG,QAyRR,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { APIError, generateCookie, parseCookies } from 'payload';
2
2
  import { formatAdminURL, getSafeRedirect } from 'payload/shared';
3
- import { getProjectToken } from '../../api/figma-api.js';
3
+ import { getProjectToken, getUserInfo } from '../../api/figma-api.js';
4
4
  import { createCookieOptions } from '../utilities/createCookieOptions.js';
5
5
  import { createDebugLogger } from '../utilities/createDebugLogger.js';
6
6
  import { extractOrigin } from '../utilities/extractOrigin.js';
@@ -167,6 +167,21 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
167
167
  });
168
168
  return Response.redirect(failedRedirect);
169
169
  }
170
+ // Fetch Figma user info for handle and profile image
171
+ let figmaUserInfo = null;
172
+ try {
173
+ figmaUserInfo = await getUserInfo(access_token);
174
+ debugLogger.info({
175
+ figmaUserInfo,
176
+ msg: 'Fetched Figma user info'
177
+ });
178
+ } catch (err) {
179
+ // Non-fatal: user will just not have handle/image
180
+ debugLogger.error({
181
+ err,
182
+ msg: 'Failed to fetch Figma user info'
183
+ });
184
+ }
170
185
  if (!access_token) {
171
186
  const errMsg = 'No access_token was returned from the identity provider.';
172
187
  req.payload.logger.error({
@@ -231,6 +246,21 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
231
246
  value: token
232
247
  });
233
248
  req.responseHeaders.append('Set-Cookie', tokenCookie);
249
+ // Store user info in short-lived cookie for defaultVerify to consume
250
+ if (figmaUserInfo) {
251
+ const userInfoValue = Buffer.from(JSON.stringify({
252
+ handle: figmaUserInfo.handle,
253
+ img_url: figmaUserInfo.img_url
254
+ })).toString('base64');
255
+ const userInfoCookie = generateCookie({
256
+ ...cookieOptions,
257
+ name: 'figma-user-info',
258
+ expires: new Date(Date.now() + 60_000),
259
+ returnCookieAsObject: false,
260
+ value: userInfoValue
261
+ });
262
+ req.responseHeaders.append('Set-Cookie', userInfoCookie);
263
+ }
234
264
  return Response.redirect(redirectToUse);
235
265
  } catch (err) {
236
266
  req.payload.logger.error({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"sourcesContent":["import type { CollectionConfig, Endpoint } from 'payload'\n\nimport { APIError, generateCookie, parseCookies } from 'payload'\nimport { formatAdminURL, getSafeRedirect } from 'payload/shared'\n\nimport type { Strategy } from '../strategy/index.js'\nimport type { CollectionOptions, PluginOptions, StateObj } from '../types.js'\n\nimport { getProjectToken } from '../../api/figma-api.js'\nimport { createCookieOptions } from '../utilities/createCookieOptions.js'\nimport { createDebugLogger } from '../utilities/createDebugLogger.js'\nimport { extractOrigin } from '../utilities/extractOrigin.js'\nimport { getCookieExpiration } from '../utilities/getCookieExpiration.js'\n\ninterface Args {\n collection: CollectionConfig\n collectionOptions: CollectionOptions\n endpointSlug: string\n pluginOptions: PluginOptions\n strategy: Strategy\n}\n\nconst isAbsoluteURL = (url: string) => /^[a-z][a-z\\d+.-]*:/.test(url)\n\nexport const getLoginEndpoint = ({\n collection,\n collectionOptions,\n endpointSlug,\n pluginOptions,\n strategy,\n}: Args): Endpoint => ({\n handler: async (req) => {\n const { config } = req.payload\n const adminRoute = config.routes?.admin || '/admin'\n\n try {\n await strategy.ensureMeta()\n\n const path = req.pathname\n const debugLogger = createDebugLogger(req.payload, pluginOptions.debug)\n\n let jsonBody: Record<string, unknown> = {}\n\n const contentType = req.headers.get('content-type')\n\n // Type narrow PayloadRequst's Partial<Request>\n if (typeof req.json !== 'function' || typeof req.formData !== 'function') {\n debugLogger.info({\n msg: 'Request does not have json or formData methods',\n path,\n })\n\n return Response.redirect(`${pluginOptions.redirectServerURL || config.serverURL}/login`)\n }\n\n if (collectionOptions.authorizationURLParams?.response_mode === 'query') {\n if (Object.keys(req.query).length > 0) {\n jsonBody = req.query\n }\n } else if (contentType === 'application/json') {\n jsonBody = await req.json()\n } else if (contentType === 'application/x-www-form-urlencoded') {\n const formData = await req.formData()\n jsonBody = Object.fromEntries(formData.entries())\n }\n\n const stateRaw = jsonBody.state as string\n const state: StateObj = JSON.parse(Buffer.from(stateRaw, 'base64').toString('utf-8'))\n\n const redirectServerURL =\n state.serverURL || pluginOptions.redirectServerURL || config.serverURL\n let failedRedirect = formatAdminURL({\n adminRoute,\n path: '/login',\n serverURL: redirectServerURL,\n })\n let redirectToUse = formatAdminURL({\n adminRoute,\n serverURL: redirectServerURL,\n })\n\n if (state.redirect) {\n redirectToUse = state.redirect\n }\n\n if (state.failedRedirect) {\n failedRedirect = state.failedRedirect\n }\n\n failedRedirect = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: failedRedirect })\n redirectToUse = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: redirectToUse })\n\n if (!isAbsoluteURL(failedRedirect)) {\n return Response.json({ error: 'Invalid failed redirect provided' }, { status: 403 })\n }\n if (!isAbsoluteURL(redirectToUse)) {\n return Response.json({ error: 'Invalid redirect provided' }, { status: 403 })\n }\n\n if (Array.isArray(req.payload.config.csrf) && req.payload.config.csrf.length > 0) {\n const failedRedirectOrigin = extractOrigin(failedRedirect)\n const redirectToUseOrigin = extractOrigin(redirectToUse)\n\n if (failedRedirectOrigin && !req.payload.config.csrf.includes(failedRedirectOrigin)) {\n req.payload.logger.error(\n `Failed redirect origin ${failedRedirectOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Failed redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n\n if (redirectToUseOrigin && !req.payload.config.csrf.includes(redirectToUseOrigin)) {\n req.payload.logger.error(\n `Redirect origin ${redirectToUseOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n }\n\n // Verify signature\n if (strategy.name !== req.payload.decrypt(state.sig)) {\n req.payload.logger.error('Signature mismatch. Failing login and redirecting.')\n return Response.redirect(failedRedirect)\n }\n\n if (!strategy.meta?.token_endpoint) {\n debugLogger.error({\n msg: `No metadata was obtained from the identity provider. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n debugLogger.info({ body: jsonBody, msg: 'Received login response body', path })\n\n if (!jsonBody?.code) {\n debugLogger.error({\n msg: `No authorization code present in body. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n // Exchange `code` from body for refresh token\n const { code } = jsonBody\n\n if (typeof code !== 'string') {\n debugLogger.error({\n msg: `Authorization code present in body is not a string. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n const body: Record<string, string> = {\n client_id: strategy.clientID,\n code,\n code_verifier: req.payload.decrypt(state.verifier),\n grant_type: 'authorization_code',\n redirect_uri: formatAdminURL({\n apiRoute: config.routes?.api || '/api',\n path: `/${collection.slug}/${endpointSlug}/login`,\n serverURL: redirectServerURL,\n }),\n }\n\n if (strategy.collectionOptions.clientSecret) {\n body.client_secret = strategy.collectionOptions.clientSecret\n }\n\n debugLogger.info({\n msg: 'Exchanging code for access token',\n path,\n requestBody: body,\n })\n\n const tokenRes = await fetch(strategy.meta?.token_endpoint, {\n body: new URLSearchParams(body),\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n method: 'POST',\n }).then((r) => r.json())\n\n debugLogger.info({ msg: 'Token Response', path, tokenRes })\n\n const { access_token, error, error_description, expires_in: refreshTokenExpires } = tokenRes\n\n if (error) {\n req.payload.logger.error('Error swapping access code with access token:')\n req.payload.logger.error({ err: error, error_description })\n return Response.redirect(failedRedirect)\n }\n\n if (!access_token) {\n const errMsg = 'No access_token was returned from the identity provider.'\n req.payload.logger.error({ msg: errMsg, path, tokenRes })\n throw new APIError(errMsg)\n }\n\n // Swap for project token\n const project_token = await getProjectToken(access_token, config.custom.figma.contentSystemId)\n const refresh_token = access_token\n\n // Store project token as access_token\n\n const token = project_token.token\n const expiresIn = project_token.expiresAt\n\n if (!token) {\n const errMsg = `No ${\n collectionOptions.token || 'access_token'\n } was returned from the identity provider.`\n req.payload.logger.error({ msg: errMsg, path })\n throw new APIError(errMsg)\n }\n\n const cookieOptions = createCookieOptions({ collection, headers: req.headers, pluginOptions })\n\n // get the payloadRedirect cookie value\n const cookies = parseCookies(req.headers)\n\n const payloadRedirect =\n 'payloadRedirect' in cookies ? (cookies.payloadRedirect as string) : ''\n\n if (!req.responseHeaders) {\n req.responseHeaders = new Headers()\n }\n\n if (payloadRedirect) {\n if (isAbsoluteURL(payloadRedirect)) {\n redirectToUse = payloadRedirect\n } else {\n redirectToUse += payloadRedirect\n }\n\n req.responseHeaders.append(\n 'Set-Cookie',\n `payloadRedirect=${redirectToUse}; Max-Age=0; Path=/;`,\n )\n }\n\n if (refresh_token && !collectionOptions.usePayloadJWT) {\n debugLogger.info('Setting refresh token cookie')\n const refreshCookie = generateCookie({\n returnCookieAsObject: false,\n ...cookieOptions,\n name: `${strategy.cookieName}-refresh`,\n value: refresh_token,\n // Default to 1 year if no expiration\n expires: getCookieExpiration(cookieOptions?.expires ?? refreshTokenExpires ?? 31_556_952),\n }) as string\n\n req.responseHeaders.append('Set-Cookie', refreshCookie)\n }\n\n const tokenExpires = cookieOptions?.expires\n ? getCookieExpiration(cookieOptions.expires)\n : new Date(expiresIn) // Project token uses epoch time\n const tokenCookie = generateCookie({\n ...cookieOptions,\n name: strategy.cookieName,\n expires: tokenExpires,\n returnCookieAsObject: false,\n value: token,\n }) as string\n\n req.responseHeaders.append('Set-Cookie', tokenCookie)\n\n return Response.redirect(redirectToUse)\n } catch (err: unknown) {\n req.payload.logger.error({ err, msg: 'Error while attempting to login', path: req.pathname })\n\n return Response.json({ error: 'Error while attempting to login' }, { status: 500 })\n }\n },\n method: collectionOptions.authorizationURLParams?.response_mode === 'query' ? 'get' : 'post',\n path: `/${endpointSlug}/login`,\n})\n"],"names":["APIError","generateCookie","parseCookies","formatAdminURL","getSafeRedirect","getProjectToken","createCookieOptions","createDebugLogger","extractOrigin","getCookieExpiration","isAbsoluteURL","url","test","getLoginEndpoint","collection","collectionOptions","endpointSlug","pluginOptions","strategy","handler","req","config","payload","adminRoute","routes","admin","ensureMeta","path","pathname","debugLogger","debug","jsonBody","contentType","headers","get","json","formData","info","msg","Response","redirect","redirectServerURL","serverURL","authorizationURLParams","response_mode","Object","keys","query","length","fromEntries","entries","stateRaw","state","JSON","parse","Buffer","from","toString","failedRedirect","redirectToUse","allowAbsoluteUrls","redirectTo","error","status","Array","isArray","csrf","failedRedirectOrigin","redirectToUseOrigin","includes","logger","name","decrypt","sig","meta","token_endpoint","body","code","client_id","clientID","code_verifier","verifier","grant_type","redirect_uri","apiRoute","api","slug","clientSecret","client_secret","requestBody","tokenRes","fetch","URLSearchParams","method","then","r","access_token","error_description","expires_in","refreshTokenExpires","err","errMsg","project_token","custom","figma","contentSystemId","refresh_token","token","expiresIn","expiresAt","cookieOptions","cookies","payloadRedirect","responseHeaders","Headers","append","usePayloadJWT","refreshCookie","returnCookieAsObject","cookieName","value","expires","tokenExpires","Date","tokenCookie"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/oauth/endpoints/getLoginEndpoint.ts"],"sourcesContent":["import type { CollectionConfig, Endpoint } from 'payload'\n\nimport { APIError, generateCookie, parseCookies } from 'payload'\nimport { formatAdminURL, getSafeRedirect } from 'payload/shared'\n\nimport type { FigmaUserInfo } from '../../api/figma-api.js'\nimport type { Strategy } from '../strategy/index.js'\nimport type { CollectionOptions, PluginOptions, StateObj } from '../types.js'\n\nimport { getProjectToken, getUserInfo } from '../../api/figma-api.js'\nimport { createCookieOptions } from '../utilities/createCookieOptions.js'\nimport { createDebugLogger } from '../utilities/createDebugLogger.js'\nimport { extractOrigin } from '../utilities/extractOrigin.js'\nimport { getCookieExpiration } from '../utilities/getCookieExpiration.js'\n\ninterface Args {\n collection: CollectionConfig\n collectionOptions: CollectionOptions\n endpointSlug: string\n pluginOptions: PluginOptions\n strategy: Strategy\n}\n\nconst isAbsoluteURL = (url: string) => /^[a-z][a-z\\d+.-]*:/.test(url)\n\nexport const getLoginEndpoint = ({\n collection,\n collectionOptions,\n endpointSlug,\n pluginOptions,\n strategy,\n}: Args): Endpoint => ({\n handler: async (req) => {\n const { config } = req.payload\n const adminRoute = config.routes?.admin || '/admin'\n\n try {\n await strategy.ensureMeta()\n\n const path = req.pathname\n const debugLogger = createDebugLogger(req.payload, pluginOptions.debug)\n\n let jsonBody: Record<string, unknown> = {}\n\n const contentType = req.headers.get('content-type')\n\n // Type narrow PayloadRequst's Partial<Request>\n if (typeof req.json !== 'function' || typeof req.formData !== 'function') {\n debugLogger.info({\n msg: 'Request does not have json or formData methods',\n path,\n })\n\n return Response.redirect(`${pluginOptions.redirectServerURL || config.serverURL}/login`)\n }\n\n if (collectionOptions.authorizationURLParams?.response_mode === 'query') {\n if (Object.keys(req.query).length > 0) {\n jsonBody = req.query\n }\n } else if (contentType === 'application/json') {\n jsonBody = await req.json()\n } else if (contentType === 'application/x-www-form-urlencoded') {\n const formData = await req.formData()\n jsonBody = Object.fromEntries(formData.entries())\n }\n\n const stateRaw = jsonBody.state as string\n const state: StateObj = JSON.parse(Buffer.from(stateRaw, 'base64').toString('utf-8'))\n\n const redirectServerURL =\n state.serverURL || pluginOptions.redirectServerURL || config.serverURL\n let failedRedirect = formatAdminURL({\n adminRoute,\n path: '/login',\n serverURL: redirectServerURL,\n })\n let redirectToUse = formatAdminURL({\n adminRoute,\n serverURL: redirectServerURL,\n })\n\n if (state.redirect) {\n redirectToUse = state.redirect\n }\n\n if (state.failedRedirect) {\n failedRedirect = state.failedRedirect\n }\n\n failedRedirect = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: failedRedirect })\n redirectToUse = getSafeRedirect({ allowAbsoluteUrls: true, redirectTo: redirectToUse })\n\n if (!isAbsoluteURL(failedRedirect)) {\n return Response.json({ error: 'Invalid failed redirect provided' }, { status: 403 })\n }\n if (!isAbsoluteURL(redirectToUse)) {\n return Response.json({ error: 'Invalid redirect provided' }, { status: 403 })\n }\n\n if (Array.isArray(req.payload.config.csrf) && req.payload.config.csrf.length > 0) {\n const failedRedirectOrigin = extractOrigin(failedRedirect)\n const redirectToUseOrigin = extractOrigin(redirectToUse)\n\n if (failedRedirectOrigin && !req.payload.config.csrf.includes(failedRedirectOrigin)) {\n req.payload.logger.error(\n `Failed redirect origin ${failedRedirectOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Failed redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n\n if (redirectToUseOrigin && !req.payload.config.csrf.includes(redirectToUseOrigin)) {\n req.payload.logger.error(\n `Redirect origin ${redirectToUseOrigin} is not allowed by CSRF configuration.`,\n )\n return Response.json(\n { error: 'Redirect origin is not allowed by CSRF configuration.' },\n { status: 403 },\n )\n }\n }\n\n // Verify signature\n if (strategy.name !== req.payload.decrypt(state.sig)) {\n req.payload.logger.error('Signature mismatch. Failing login and redirecting.')\n return Response.redirect(failedRedirect)\n }\n\n if (!strategy.meta?.token_endpoint) {\n debugLogger.error({\n msg: `No metadata was obtained from the identity provider. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n debugLogger.info({ body: jsonBody, msg: 'Received login response body', path })\n\n if (!jsonBody?.code) {\n debugLogger.error({\n msg: `No authorization code present in body. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n // Exchange `code` from body for refresh token\n const { code } = jsonBody\n\n if (typeof code !== 'string') {\n debugLogger.error({\n msg: `Authorization code present in body is not a string. Failing login and redirecting.`,\n path,\n })\n return Response.redirect(failedRedirect)\n }\n\n const body: Record<string, string> = {\n client_id: strategy.clientID,\n code,\n code_verifier: req.payload.decrypt(state.verifier),\n grant_type: 'authorization_code',\n redirect_uri: formatAdminURL({\n apiRoute: config.routes?.api || '/api',\n path: `/${collection.slug}/${endpointSlug}/login`,\n serverURL: redirectServerURL,\n }),\n }\n\n if (strategy.collectionOptions.clientSecret) {\n body.client_secret = strategy.collectionOptions.clientSecret\n }\n\n debugLogger.info({\n msg: 'Exchanging code for access token',\n path,\n requestBody: body,\n })\n\n const tokenRes = await fetch(strategy.meta?.token_endpoint, {\n body: new URLSearchParams(body),\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n method: 'POST',\n }).then((r) => r.json())\n\n debugLogger.info({ msg: 'Token Response', path, tokenRes })\n\n const { access_token, error, error_description, expires_in: refreshTokenExpires } = tokenRes\n\n if (error) {\n req.payload.logger.error('Error swapping access code with access token:')\n req.payload.logger.error({ err: error, error_description })\n return Response.redirect(failedRedirect)\n }\n\n // Fetch Figma user info for handle and profile image\n let figmaUserInfo: FigmaUserInfo | null = null\n try {\n figmaUserInfo = await getUserInfo(access_token)\n debugLogger.info({ figmaUserInfo, msg: 'Fetched Figma user info' })\n } catch (err) {\n // Non-fatal: user will just not have handle/image\n debugLogger.error({ err, msg: 'Failed to fetch Figma user info' })\n }\n\n if (!access_token) {\n const errMsg = 'No access_token was returned from the identity provider.'\n req.payload.logger.error({ msg: errMsg, path, tokenRes })\n throw new APIError(errMsg)\n }\n\n // Swap for project token\n const project_token = await getProjectToken(access_token, config.custom.figma.contentSystemId)\n const refresh_token = access_token\n\n // Store project token as access_token\n\n const token = project_token.token\n const expiresIn = project_token.expiresAt\n\n if (!token) {\n const errMsg = `No ${\n collectionOptions.token || 'access_token'\n } was returned from the identity provider.`\n req.payload.logger.error({ msg: errMsg, path })\n throw new APIError(errMsg)\n }\n\n const cookieOptions = createCookieOptions({ collection, headers: req.headers, pluginOptions })\n\n // get the payloadRedirect cookie value\n const cookies = parseCookies(req.headers)\n\n const payloadRedirect =\n 'payloadRedirect' in cookies ? (cookies.payloadRedirect as string) : ''\n\n if (!req.responseHeaders) {\n req.responseHeaders = new Headers()\n }\n\n if (payloadRedirect) {\n if (isAbsoluteURL(payloadRedirect)) {\n redirectToUse = payloadRedirect\n } else {\n redirectToUse += payloadRedirect\n }\n\n req.responseHeaders.append(\n 'Set-Cookie',\n `payloadRedirect=${redirectToUse}; Max-Age=0; Path=/;`,\n )\n }\n\n if (refresh_token && !collectionOptions.usePayloadJWT) {\n debugLogger.info('Setting refresh token cookie')\n const refreshCookie = generateCookie({\n returnCookieAsObject: false,\n ...cookieOptions,\n name: `${strategy.cookieName}-refresh`,\n value: refresh_token,\n // Default to 1 year if no expiration\n expires: getCookieExpiration(cookieOptions?.expires ?? refreshTokenExpires ?? 31_556_952),\n }) as string\n\n req.responseHeaders.append('Set-Cookie', refreshCookie)\n }\n\n const tokenExpires = cookieOptions?.expires\n ? getCookieExpiration(cookieOptions.expires)\n : new Date(expiresIn) // Project token uses epoch time\n const tokenCookie = generateCookie({\n ...cookieOptions,\n name: strategy.cookieName,\n expires: tokenExpires,\n returnCookieAsObject: false,\n value: token,\n }) as string\n\n req.responseHeaders.append('Set-Cookie', tokenCookie)\n\n // Store user info in short-lived cookie for defaultVerify to consume\n if (figmaUserInfo) {\n const userInfoValue = Buffer.from(\n JSON.stringify({\n handle: figmaUserInfo.handle,\n img_url: figmaUserInfo.img_url,\n }),\n ).toString('base64')\n const userInfoCookie = generateCookie({\n ...cookieOptions,\n name: 'figma-user-info',\n expires: new Date(Date.now() + 60_000), // 60 seconds\n returnCookieAsObject: false,\n value: userInfoValue,\n }) as string\n req.responseHeaders.append('Set-Cookie', userInfoCookie)\n }\n\n return Response.redirect(redirectToUse)\n } catch (err: unknown) {\n req.payload.logger.error({ err, msg: 'Error while attempting to login', path: req.pathname })\n\n return Response.json({ error: 'Error while attempting to login' }, { status: 500 })\n }\n },\n method: collectionOptions.authorizationURLParams?.response_mode === 'query' ? 'get' : 'post',\n path: `/${endpointSlug}/login`,\n})\n"],"names":["APIError","generateCookie","parseCookies","formatAdminURL","getSafeRedirect","getProjectToken","getUserInfo","createCookieOptions","createDebugLogger","extractOrigin","getCookieExpiration","isAbsoluteURL","url","test","getLoginEndpoint","collection","collectionOptions","endpointSlug","pluginOptions","strategy","handler","req","config","payload","adminRoute","routes","admin","ensureMeta","path","pathname","debugLogger","debug","jsonBody","contentType","headers","get","json","formData","info","msg","Response","redirect","redirectServerURL","serverURL","authorizationURLParams","response_mode","Object","keys","query","length","fromEntries","entries","stateRaw","state","JSON","parse","Buffer","from","toString","failedRedirect","redirectToUse","allowAbsoluteUrls","redirectTo","error","status","Array","isArray","csrf","failedRedirectOrigin","redirectToUseOrigin","includes","logger","name","decrypt","sig","meta","token_endpoint","body","code","client_id","clientID","code_verifier","verifier","grant_type","redirect_uri","apiRoute","api","slug","clientSecret","client_secret","requestBody","tokenRes","fetch","URLSearchParams","method","then","r","access_token","error_description","expires_in","refreshTokenExpires","err","figmaUserInfo","errMsg","project_token","custom","figma","contentSystemId","refresh_token","token","expiresIn","expiresAt","cookieOptions","cookies","payloadRedirect","responseHeaders","Headers","append","usePayloadJWT","refreshCookie","returnCookieAsObject","cookieName","value","expires","tokenExpires","Date","tokenCookie","userInfoValue","stringify","handle","img_url","userInfoCookie","now"],"mappings":"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"}
@@ -11,7 +11,7 @@ import type { Config, SanitizedConfig } from 'payload';
11
11
  * // Minimal config
12
12
  * const config: FigmaConfig = {
13
13
  * figma: {
14
- * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!
14
+ * contentSystemId: process.env.FIGMA_CONTENT_API_CONTENT_SYSTEM_ID!
15
15
  * },
16
16
  * collections: [...]
17
17
  * }
@@ -20,7 +20,7 @@ import type { Config, SanitizedConfig } from 'payload';
20
20
  * // With custom editor
21
21
  * const config: FigmaConfig = {
22
22
  * figma: {
23
- * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!
23
+ * contentSystemId: process.env.FIGMA_CONTENT_API_CONTENT_SYSTEM_ID!
24
24
  * },
25
25
  * collections: [...],
26
26
  * editor: lexicalEditor({ features: [...] })
@@ -30,7 +30,7 @@ import type { Config, SanitizedConfig } from 'payload';
30
30
  * // Disable Content System (use custom db)
31
31
  * const config: FigmaConfig = {
32
32
  * figma: {
33
- * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!,
33
+ * contentSystemId: process.env.FIGMA_CONTENT_API_CONTENT_SYSTEM_ID!,
34
34
  * useContentSystem: false
35
35
  * },
36
36
  * db: mongooseAdapter({ url: process.env.DATABASE_URI }),
@@ -1 +1 @@
1
- {"version":3,"file":"build-config.d.ts","sourceRoot":"","sources":["../../src/plugin/build-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAkBtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;CAC1B,GAAG;IACF,KAAK,EAAE;QACL,eAAe,EAAE,MAAM,CAAA;QACvB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;CACF,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;AAE7B;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CA8HpF"}
1
+ {"version":3,"file":"build-config.d.ts","sourceRoot":"","sources":["../../src/plugin/build-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAS,eAAe,EAAE,MAAM,SAAS,CAAA;AAmB7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;CAC1B,GAAG;IACF,KAAK,EAAE;QACL,eAAe,EAAE,MAAM,CAAA;QACvB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;CACF,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;AA6B7B;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CA8IpF"}
@@ -6,10 +6,36 @@ import { getTokenStore } from '../auth/token-store.js';
6
6
  import { ENV_CONFIG, getEnvironment } from '../constants.js';
7
7
  import { contentAPIAdapter } from '../db-content-api/index.js';
8
8
  import { health } from '../endpoints/health.js';
9
+ import { schema } from '../endpoints/schema.js';
9
10
  import { oAuth2Plugin } from '../oauth/index.js';
10
11
  import { createStorageClient } from '../storage-content-api/client.js';
11
12
  import { getGenerateSignedURLHandler } from '../storage-content-api/client-uploads/generateSignedURL.js';
12
13
  import { contentApiStorageAdapter } from '../storage-content-api/index.js';
14
+ /**
15
+ * Fields added to users collection for Figma profile info
16
+ */ const figmaUserFields = [
17
+ {
18
+ name: 'figmaHandle',
19
+ type: 'text',
20
+ admin: {
21
+ readOnly: true
22
+ },
23
+ label: 'Name'
24
+ },
25
+ {
26
+ name: 'figmaImageUrl',
27
+ type: 'text',
28
+ admin: {
29
+ components: {
30
+ Cell: '@payloadcms/figma/client#FigmaAvatarCell'
31
+ },
32
+ disableBulkEdit: true,
33
+ hidden: true,
34
+ readOnly: true
35
+ },
36
+ label: 'Profile Picture'
37
+ }
38
+ ];
13
39
  /**
14
40
  * Figma platform wrapper for Payload's buildConfig.
15
41
  * Automatically injects:
@@ -22,7 +48,7 @@ import { contentApiStorageAdapter } from '../storage-content-api/index.js';
22
48
  */ export async function buildFigmaConfig(config) {
23
49
  // Validate required environment variable
24
50
  if (!config.figma.contentSystemId) {
25
- throw new Error('FIGMA_CONTENT_SYSTEM_ID is required. ' + 'Ensure your .env file contains: FIGMA_CONTENT_SYSTEM_ID=your-content-system-id');
51
+ throw new Error('FIGMA_CONTENT_API_CONTENT_SYSTEM_ID is required. ' + 'Ensure your .env file contains: FIGMA_CONTENT_API_CONTENT_SYSTEM_ID=your-content-system-id');
26
52
  }
27
53
  const env = getEnvironment();
28
54
  const url = process.env.FIGMA_CONTENT_API_URL || ENV_CONFIG[env].contentApiUrl;
@@ -107,9 +133,10 @@ import { contentApiStorageAdapter } from '../storage-content-api/index.js';
107
133
  ...config,
108
134
  admin: {
109
135
  // Auto-refresh tokens before expiry to prevent unexpected logouts.
110
- // The project token (access token) expires in ~15 minutes, so the client
111
- // will automatically refresh at ~14 minutes to keep the session alive.
112
136
  autoRefresh: true,
137
+ avatar: {
138
+ Component: '@payloadcms/figma/client#FigmaAvatar'
139
+ },
113
140
  ...config.admin
114
141
  },
115
142
  custom: {
@@ -119,7 +146,8 @@ import { contentApiStorageAdapter } from '../storage-content-api/index.js';
119
146
  editor: config.editor ?? lexicalEditor(),
120
147
  endpoints: [
121
148
  ...config.endpoints ?? [],
122
- health
149
+ health,
150
+ schema
123
151
  ],
124
152
  secret: 'MY_SECRET',
125
153
  // Add oauth to plugins if not already present
@@ -152,6 +180,22 @@ import { contentApiStorageAdapter } from '../storage-content-api/index.js';
152
180
  })
153
181
  ]
154
182
  };
183
+ // Inject figma fields into users collection
184
+ const collections = configWithFigmaDefaults.collections?.map((col)=>{
185
+ if (col.slug === 'users') {
186
+ return {
187
+ ...col,
188
+ fields: [
189
+ ...col.fields || [],
190
+ ...figmaUserFields
191
+ ]
192
+ };
193
+ }
194
+ return col;
195
+ });
196
+ if (collections) {
197
+ configWithFigmaDefaults.collections = collections;
198
+ }
155
199
  // Delegate to Payload's buildConfig
156
200
  return await payloadBuildConfig(configWithFigmaDefaults);
157
201
  }