Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
9f7417d
fix: send email verification on existing user sign up
boazpoolman Aug 10, 2026
54406a6
fix: avatar uploading
boazpoolman Aug 10, 2026
c500d9b
feat: replace email-otp plugin with magic-link, send additional email…
boazpoolman Aug 10, 2026
5944eab
feat: organization management
boazpoolman Aug 10, 2026
153f78c
fix: allow any org admin/owner to update the logo
boazpoolman Aug 11, 2026
a6867ee
feat: add additional API endpoints for managing your personal profile
boazpoolman Sep 10, 2026
bc80fc1
feat: personal profile tab in setting section
boazpoolman Sep 10, 2026
923f6a7
fix: merge conflicts
boazpoolman Sep 10, 2026
88e31d0
chore: refactor component structure
boazpoolman Sep 10, 2026
6d4f20a
feat: add validation for the profile pages
boazpoolman Sep 10, 2026
8f856d1
fix: profile rendering
boazpoolman Sep 10, 2026
cddedc1
feat: add 'slug' field to the user ct for users to manually adjust
boazpoolman Sep 10, 2026
419a376
chore: update copy
boazpoolman Sep 10, 2026
c5260cc
feat: add a 'profile' link to the user button dropdown
boazpoolman Sep 10, 2026
5e5e90e
feat: add settings context switching
boazpoolman Sep 11, 2026
b015fc8
feat: update auth toast styling
boazpoolman Sep 13, 2026
3477b4c
feat: redirect to /account page when logged in
boazpoolman Sep 13, 2026
5fbc115
fix: show error when using the wrong image format for logo/avatar
boazpoolman Sep 13, 2026
6033644
fix: add correct HTML titles
boazpoolman Sep 13, 2026
2927a2e
feat: enhance org invitation flow
boazpoolman Sep 13, 2026
85d4e1b
feat: cascade deletion for users and organizations
boazpoolman Sep 13, 2026
5be222d
feat: add a boolean to opt-out of the readme syncing
boazpoolman Sep 14, 2026
7989f0a
feat: add API's for updating submissions
boazpoolman Sep 14, 2026
8c048d5
feat: add interface for managing submissions
boazpoolman Sep 14, 2026
c77660c
Merge pull request #87 from strapi/feature/personal-profile-settings
boazpoolman Sep 14, 2026
1d57cf7
chore: security precautions
boazpoolman Sep 15, 2026
b0bf4de
fix: use the production URL for email assets
boazpoolman Sep 15, 2026
8bb40d2
chore: disable content transferuntil we've implemented two-way transf…
boazpoolman Sep 15, 2026
14dd050
chore: prevent all custom & authenticated endpoints from being reques…
boazpoolman Sep 18, 2026
d25ff31
chore: sanitize uploaded svg's
boazpoolman Sep 18, 2026
4df052c
fix: set same-site to lax always
boazpoolman Sep 18, 2026
4943395
fix: vercel deployment
boazpoolman Sep 18, 2026
97eca1e
fix: vercel deployment
boazpoolman Sep 18, 2026
918f53c
fix: don't try to populate private fields
boazpoolman Sep 21, 2026
89488d2
chore: disable social login for now
boazpoolman Sep 21, 2026
6d201bc
fix: avatar overlapping issue in the /account seciton
boazpoolman Sep 21, 2026
b03eeca
feat: only allow logged in submissions
boazpoolman Sep 21, 2026
694fcc5
feat: add character limits to some of the submission/profile fields
boazpoolman Sep 21, 2026
b7850f0
feat: gate submissions edits while it's under review
boazpoolman Sep 21, 2026
51912e1
feat: select submission owner in submit forms
boazpoolman Sep 21, 2026
18d1a8c
feat: setup the account-access-email
boazpoolman Sep 21, 2026
181d9de
chore: disable magic link sign-up
boazpoolman Sep 21, 2026
3fe7155
fix: allow editing submissions without an overall_status
boazpoolman Sep 21, 2026
9e9b619
chore: update the Better Auth app name
boazpoolman Sep 21, 2026
920e814
feat: enable Google and Github auth
boazpoolman Sep 24, 2026
8ebec51
chore: update better auth schema
boazpoolman Sep 24, 2026
9f5b59b
feat: new screen for email verification
boazpoolman Sep 25, 2026
3f5cd63
feat: custom org invitation accept flow
boazpoolman Oct 5, 2026
22713bd
fix: don't send a magic link to non existing email addresses
boazpoolman Oct 5, 2026
40643af
fix: update the max lengths
boazpoolman Oct 5, 2026
c001f46
fix: disable social login for now
boazpoolman Oct 9, 2026
e6f168f
Merge pull request #93 from strapi/feature/fix-auth-feedback
boazpoolman Oct 9, 2026
4ab5ad6
fix: enable social auth
boazpoolman Oct 9, 2026
52ccdda
fix: set redirect uri as website url for social auth
boazpoolman Oct 9, 2026
28edb2d
fix: remove redirect uri from social providers
boazpoolman Oct 9, 2026
1475e44
feat: add better callback error handling
boazpoolman Oct 9, 2026
2a6e524
fix: oauth callback issues
boazpoolman Oct 9, 2026
225abe7
Merge branch 'main' of github.com:strapi/marketplace into develop
boazpoolman Oct 19, 2026
ce4e2b2
feat: fix issues with the submit form
boazpoolman Oct 19, 2026
c60c8a8
fix: temporarily disable maintainer editing
boazpoolman Oct 19, 2026
8331eb7
fix: allow avatars from Github and Google
boazpoolman Oct 19, 2026
2f728e7
feat: add submit buttons to the settings section
boazpoolman Oct 20, 2026
1227f46
feat: add a migration script for sending account access emails
boazpoolman Oct 21, 2026
4b6d9f5
fix: better error handling for migrations
boazpoolman Oct 21, 2026
adea19b
chore: security fixes for file uploads
boazpoolman Oct 22, 2026
a5f2d48
feat: show submit button when logged in
boazpoolman Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/cms/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ ENCRYPTION_KEY=tobemodified
BETTER_AUTH_DEBUG=false
BETTER_AUTH_SECRET=tobemodified
BETTER_AUTH_URL=tobemodified
# Authenticates the better-auth "dash" dashboard integration (lib/auth.ts).
# Required — there is no insecure fallback if this is left unset.
BETTER_AUTH_DASHBOARD_SECRET=tobemodified

# Social sign-in for the community hub (better-auth). Distinct from the
# "Strapi SSO" GOOGLE_CLIENT_ID/SECRET below, which authenticate the Strapi
# admin panel — these need their own OAuth app(s) with redirect URIs set to
# {BETTER_AUTH_URL}/api/auth/callback/google and .../callback/github.
BETTER_AUTH_GOOGLE_CLIENT_ID=tobemodified
BETTER_AUTH_GOOGLE_CLIENT_SECRET=tobemodified
BETTER_AUTH_GITHUB_CLIENT_ID=tobemodified
BETTER_AUTH_GITHUB_CLIENT_SECRET=tobemodified

WEBSITE_URL=http://localhost:3000
SITE_NAME=Strapi Community
Expand Down
19 changes: 17 additions & 2 deletions apps/cms/config/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default [
export default ({ env }) => [
"strapi::logger",
"strapi::errors",
{
Expand Down Expand Up @@ -27,7 +27,22 @@ export default [
},
},
},
"strapi::cors",
{
name: "strapi::cors",
config: {
// Strapi's own default (`origin: '*'`) reflects *any* request Origin
// verbatim while still sending `Access-Control-Allow-Credentials:
// true` — effectively "allow every website, with cookies". The
// better-auth extension's custom content-api routes (organizations/
// users/packages/templates — see `extensions/better-auth`) are
// deliberately cookie-authenticated (`is-authenticated` policy) with
// no CSRF token of their own, so an unrestricted origin here would
// let any external site ride a logged-in visitor's session to read
// or modify their account data. Restrict to the web app's own origin
// instead.
origin: [env("WEBSITE_URL")],
},
},
"strapi::poweredBy",
"strapi::query",
"strapi::body",
Expand Down
21 changes: 21 additions & 0 deletions apps/cms/config/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ export default ({ env }) => ({
defaultFieldValues: {
labels: { official: false, featured: false, paid: false },
},
submitFields: [
"name",
"slug",
"description",
"git_repository",
"package_location",
"type",
"readme",
"readme_auto_sync",
"icon",
],
checks: [
"repo_public",
"readme_exists",
Expand All @@ -66,6 +77,16 @@ export default ({ env }) => ({
defaultFieldValues: {
labels: { official: false, featured: false, paid: false },
},
submitFields: [
"name",
"slug",
"description",
"git_repository",
"preview_link",
"readme",
"readme_auto_sync",
"preview_image",
],
checks: ["repo_public", "readme_exists", "mit_license"],
webhooks: {
submissionReceived: `${N8N_WEBHOOK_NS}/template-submission-received`,
Expand Down
7 changes: 6 additions & 1 deletion apps/cms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"upgrade:dry": "npx @strapi/upgrade latest --dry"
},
"dependencies": {
"@better-auth-ui/react": "^1.6.3",
"@better-auth-ui/react": "^1.6.44",
"@better-auth/infra": "^0.2.5",
"@piksail/strapi-provider-email-mailpit": "^1.0.0",
"@react-email/render": "^1.4.0",
Expand All @@ -31,13 +31,17 @@
"@strapi/strapi": "catalog:strapi",
"@strapi/types": "catalog:strapi",
"@strapi/typescript-utils": "catalog:strapi",
"@strapi/utils": "catalog:strapi",
"airtable": "^0.12.2",
"better-auth": "catalog:better-auth",
"better-sqlite3": "12.4.1",
"isomorphic-dompurify": "^4.2.0",
"lodash": "^4.17.21",
"passport-google-oauth2": "^0.2.0",
"pg": "^8.21.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-email": "^6.6.8",
"react-query": "^3.39.3",
"react-router-dom": "^6.0.0",
"strapi-lucide-icons": "^1.14.0",
Expand All @@ -50,6 +54,7 @@
"devDependencies": {
"@biomejs/biome": "^2.3.11",
"@repo/biome-config": "workspace:*",
"@types/lodash": "^4.17.13",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
Expand Down
19 changes: 14 additions & 5 deletions apps/cms/src/api/package/content-types/package/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
},
"name": {
"type": "string",
"required": true
"required": true,
"maxLength": 50
},
"description": {
"type": "text",
"required": true
"required": true,
"maxLength": 300
},
"icon": {
"type": "media",
Expand All @@ -37,10 +39,12 @@
"allowedTypes": ["images", "files", "videos", "audios"]
},
"git_repository": {
"type": "string"
"type": "string",
"required": true
},
"package_location": {
"type": "string"
"type": "string",
"required": true
},
"type": {
"type": "enumeration",
Expand Down Expand Up @@ -125,14 +129,19 @@
]
},
"submitter_ip": {
"type": "string"
"type": "string",
"private": true
},
"submitter_agreed_to_terms": {
"type": "boolean",
"default": false
},
"submission_notes": {
"type": "text"
},
"readme_auto_sync": {
"type": "boolean",
"default": true
}
}
}
82 changes: 81 additions & 1 deletion apps/cms/src/api/package/controllers/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,84 @@

import { factories } from "@strapi/strapi";

export default factories.createCoreController("api::package.package");
const READ_ONLY_FIELDS = ["git_repository", "package_location"] as const;

const EDITABLE_FIELDS = [
"name",
"description",
// "icon" — deliberately not editable here: a media field written by id
// would let the caller point it at *any* file in the media library, which
// the icon upload route would then delete as the "previous" icon. Icons
// only change through `POST /packages/:documentId/icon`.
"categories",
// "maintainers" — temporarily not editable, pending an invite flow so the
// added user consents first (see the commented-out `MaintainerPicker` in
// apps/web/.../submission-edit-form.tsx). Re-add once that's built.
"readme",
"readme_auto_sync",
] as const;

export default factories.createCoreController("api::package.package", () => ({
/**
* PUT /api/packages/:id — reused by the web app's "Submissions" edit
* form. The route's `is-content-owner` policy already restricts who
* can reach this action at all (see `routes/package.ts`); this
* override adds three more layers on top of that:
*
* - blocks the edit entirely unless the submission is already
* `approved` — while it's still `submitted`/`under_review`/
* `changes_requested`/`rejected`, moderation is reviewing (or has
* reviewed) the content as originally submitted, so the owner can't
* change it out from under that review. Entries predating the
* moderation workflow have no `overall_status` at all (`null`), so
* those are left editable rather than permanently locked out;
* - rejects the request outright if `git_repository`/`package_location`
* are present and differ from the current value — the edit form
* shows these as read-only ("we do not support updating this atm"),
* this is the server-side backstop for anyone calling the API
* directly;
* - whitelists everything else down to just the fields the edit form
* actually exposes, so nothing else can be smuggled into this
* content-api-facing write path.
*/
async update(ctx) {
const entry = ctx.state.contentEntry as Record<string, unknown>;
const documentId = ctx.params.id as string;
const incoming = (ctx.request.body?.data ?? {}) as Record<string, unknown>;

if (entry.overall_status && entry.overall_status !== "approved") {
return ctx.badRequest(
"This submission cannot be edited until it has been approved.",
);
}

for (const field of READ_ONLY_FIELDS) {
if (field in incoming && incoming[field] !== entry[field]) {
return ctx.badRequest(`${field} cannot be changed.`);
}
}

ctx.request.body.data = Object.fromEntries(
Object.entries(incoming).filter(([key]) =>
(EDITABLE_FIELDS as readonly string[]).includes(key),
),
);

const result = await super.update(ctx);

// Per product decision, an owner's edit applies immediately — no
// re-entering moderation review. If this submission was already
// approved and published, republish right away so the live version
// picks up the change. A submission still awaiting its first
// approval has no published version yet; leave it as a draft so
// moderation still gates when it first goes live.
const published = await strapi
.documents("api::package.package")
.findOne({ documentId, status: "published", fields: ["documentId"] });
if (published) {
await strapi.documents("api::package.package").publish({ documentId });
}

return result;
},
}));
19 changes: 18 additions & 1 deletion apps/cms/src/api/package/routes/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,21 @@

import { factories } from "@strapi/strapi";

export default factories.createCoreRouter("api::package.package");
export default factories.createCoreRouter("api::package.package", {
config: {
update: {
// Reused by the web app's "Submissions" edit form, called directly
// from the browser with a better-auth session cookie rather than a
// Strapi API token — same reasoning as `organization.updateProfile`
// (apps/cms/src/extensions/better-auth/routes/organization.ts).
auth: false,
policies: [
"plugin::better-auth.is-authenticated",
{
name: "plugin::better-auth.is-content-owner",
config: { uid: "api::package.package" },
},
],
},
},
});
6 changes: 4 additions & 2 deletions apps/cms/src/api/profile/content-types/profile/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
},
"attributes": {
"bio": {
"type": "text"
"type": "text",
"maxLength": 300
},
"subtitle": {
"type": "string"
"type": "string",
"maxLength": 50
},
"website": {
"type": "string"
Expand Down
13 changes: 10 additions & 3 deletions apps/cms/src/api/template/content-types/template/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
},
"name": {
"type": "string",
"required": true
"required": true,
"maxLength": 50
},
"description": {
"type": "text"
"type": "text",
"maxLength": 300
},
"git_repository": {
"type": "string"
Expand Down Expand Up @@ -112,14 +114,19 @@
]
},
"submitter_ip": {
"type": "string"
"type": "string",
"private": true
},
"submitter_agreed_to_terms": {
"type": "boolean",
"default": false
},
"submission_notes": {
"type": "text"
},
"readme_auto_sync": {
"type": "boolean",
"default": true
}
}
}
Loading
Loading