diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 362c2fa3..50411fae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,27 +1,180 @@ name: Release on: - push: - tags: - - "v*.*.*" + workflow_dispatch: + inputs: + bump: + description: Version bump (auto uses conventional commits) + type: choice + default: auto + options: + - auto + - major + - minor + - patch + +concurrency: + group: release + cancel-in-progress: false permissions: - contents: write - packages: write - id-token: write - attestations: write - artifact-metadata: write + contents: read + +defaults: + run: + shell: bash jobs: - release: + prepare: + name: Prepare release + runs-on: depot-ubuntu-latest + outputs: + tag: ${{ steps.version.outputs.tag }} + commit: ${{ steps.commit.outputs.commit }} + + steps: + - name: Require the main branch + run: | + if [[ "$GITHUB_REF" != refs/heads/main ]]; then + echo "::error::Releases must be triggered from main." + exit 1 + fi + + - name: Checkout release source + uses: actions/checkout@v6 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Setup git-cliff + uses: taiki-e/install-action@v2 + with: + tool: git-cliff@2.14.2 + + - name: Calculate next version + id: version + env: + BUMP: ${{ inputs.bump }} + run: | + version=$(git cliff --bumped-version --unreleased --offline --bump "$BUMP") + version=${version#v} + if [[ "$version" == "$(cat .version)" ]]; then + echo "No commits requiring a version bump; no release created." >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=v$version" >> "$GITHUB_OUTPUT" + echo "Preparing release v$version from $GITHUB_SHA." >> "$GITHUB_STEP_SUMMARY" + + - name: Setup pnpm + if: steps.version.outputs.tag != '' + uses: pnpm/action-setup@v6 + with: + run_install: false + + - name: Setup Node.js + if: steps.version.outputs.tag != '' + uses: actions/setup-node@v6.5.0 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + if: steps.version.outputs.tag != '' + run: pnpm install --frozen-lockfile + + - name: Update version and changelog + if: steps.version.outputs.tag != '' + env: + VERSION: ${{ steps.version.outputs.version }} + RELEASE_TAG: ${{ steps.version.outputs.tag }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + printf '%s\n' "$VERSION" > .version + jq --arg version "$VERSION" '.version = $version' frontend/package.json > "$RUNNER_TEMP/package.json" + mv "$RUNNER_TEMP/package.json" frontend/package.json + pnpm --dir frontend exec prettier --write package.json + git cliff --prepend CHANGELOG.md --tag "$RELEASE_TAG" --unreleased + + - name: Create bot app token + if: steps.version.outputs.tag != '' + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.BOT_APP_CLIENT_ID }} + private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} + permission-contents: write + + - name: Commit and tag release + if: steps.version.outputs.tag != '' + id: commit + env: + VERSION: ${{ steps.version.outputs.version }} + RELEASE_TAG: ${{ steps.version.outputs.tag }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + # Attribute the release commit to the app and authenticate the push with its installation token + bot_name="$APP_SLUG[bot]" + bot_id=$(gh api "users/$bot_name" --jq .id) + git config user.name "$bot_name" + git config user.email "$bot_id+$bot_name@users.noreply.github.com" + gh auth setup-git + + git add .version frontend/package.json CHANGELOG.md + git commit -m "release: $VERSION" + git tag "$RELEASE_TAG" + + # Publish both refs together so a concurrent main update cannot leave an orphaned release tag + git push --atomic origin HEAD:refs/heads/main "refs/tags/$RELEASE_TAG" + echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + build: + name: Build and attest + needs: prepare + if: needs.prepare.outputs.tag != '' runs-on: depot-ubuntu-24.04-16 + permissions: + contents: write + packages: write + id-token: write + attestations: write + artifact-metadata: write + env: + RELEASE_TAG: ${{ needs.prepare.outputs.tag }} steps: - name: Checkout code uses: actions/checkout@v6 with: + ref: ${{ needs.prepare.outputs.commit }} fetch-depth: 0 + - name: Create bot app token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.BOT_APP_CLIENT_ID }} + private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} + permission-contents: write + + - name: Create draft release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + # Reuse the draft when retrying a failed build + if is_draft=$(gh release view "$RELEASE_TAG" --json isDraft --jq .isDraft); then + if [[ "$is_draft" != true ]]; then + echo "::error::Release $RELEASE_TAG is already published." + exit 1 + fi + else + awk '/^## v[0-9]/ { if (found) exit; found=1; next } found' CHANGELOG.md > "$RUNNER_TEMP/release-notes.md" + gh release create "$RELEASE_TAG" --verify-tag --title "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-notes.md" --draft + fi + - name: Setup pnpm uses: pnpm/action-setup@v6 with: @@ -39,6 +192,11 @@ jobs: go-version-file: backend/go.mod cache-dependency-path: backend/go.sum + - name: Setup Syft + uses: anchore/sbom-action/download-syft@v0.24.2 + with: + syft-version: v1.52.0 + - name: Set up Depot CLI uses: depot/setup-action@v1 @@ -85,7 +243,25 @@ jobs: with: subject-checksums: ./dist/digests.txt + publish: + name: Publish release + needs: [prepare, build] + runs-on: depot-ubuntu-latest + + steps: + - name: Create bot app token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.BOT_APP_CLIENT_ID }} + private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }} + permission-contents: write + - name: Publish release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release edit ${{ github.ref_name }} --draft=false + GH_TOKEN: ${{ steps.app-token.outputs.token }} + GH_REPO: ${{ github.repository }} + RELEASE_TAG: ${{ needs.prepare.outputs.tag }} + run: | + gh release edit "$RELEASE_TAG" --draft=false + echo "Published [$RELEASE_TAG](https://github.com/$GH_REPO/releases/tag/$RELEASE_TAG)." >> "$GITHUB_STEP_SUMMARY" diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 34408144..9bf3c7e4 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -9,6 +9,9 @@ before: hooks: - pnpm install --frozen-lockfile - pnpm -C frontend build + - mkdir -p .tmp + - mv backend/frontend/dist/cyclonedx/frontend.cdx.json .tmp/frontend.cdx.json + - rm -r backend/frontend/dist/cyclonedx builds: - id: pocket-id @@ -91,6 +94,23 @@ archives: checksum: name_template: checksums.txt +sboms: + - id: binaries + artifacts: binary + disable: '{{ if index .Env "BUILD_NEXT" }}true{{ end }}' + documents: + - "pocket-id_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ targetVariant . }}.sbom.spdx.json" + cmd: sh + args: + - ../scripts/development/generate-binary-sbom.sh + - $artifact + - $document + - "pocket-id_{{ .Os }}_{{ .Arch }}{{ targetVariant . }}" + - "{{ .Version }}" + env: + - SYFT_CHECK_FOR_APP_UPDATE=false + - SYFT_CACHE_DIR= + docker_digest: name_template: digests.txt @@ -124,6 +144,7 @@ dockers_v2: sbom: true extra_files: - scripts/docker + - .tmp/frontend.cdx.json - id: pocket-id-distroless ids: @@ -152,6 +173,8 @@ dockers_v2: "org.opencontainers.image.title": "Pocket ID" disable: '{{ if index .Env "BUILD_NEXT" }}true{{ end }}' sbom: true + extra_files: + - .tmp/frontend.cdx.json - id: pocket-id-next ids: @@ -179,6 +202,7 @@ dockers_v2: sbom: true extra_files: - scripts/docker + - .tmp/frontend.cdx.json - id: pocket-id-next-distroless ids: @@ -204,6 +228,8 @@ dockers_v2: "org.opencontainers.image.title": "Pocket ID" disable: '{{ if not (index .Env "BUILD_NEXT") }}true{{ end }}' sbom: true + extra_files: + - .tmp/frontend.cdx.json notarize: macos: diff --git a/.version b/.version index 68e69e40..75249069 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.15.0 +2.16.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 60a4e1ae..751d0efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## v2.16.0 + +### Features + +- update the design of the email templates ([3249b0d](https://github.com/pocket-id/pocket-id/commit/3249b0dc8c00220b74e1008f94812cc02f6313c1) by @stonith404) + +### Bug Fixes + +- loading indicator not visible ([4ba2899](https://github.com/pocket-id/pocket-id/commit/4ba28992ff496caa20b6abe6f614eeb1fa777469) by @stonith404) +- login with Yubikey not working in some Safari browsers ([4858db3](https://github.com/pocket-id/pocket-id/commit/4858db3bb4cf6619cad8682da61f0e793f24874c) by @stonith404) + +### Other + +- upgrade git cliff version ([98f9e39](https://github.com/pocket-id/pocket-id/commit/98f9e39785ae913d65ab3ad3523feaf907d3686e) by @stonith404) +- update francis to rc7 ([\#1769](https://github.com/pocket-id/pocket-id/pull/1769) by @ItalyPaleAle) +- include frontend dependencies in SBOM ([5a1c6f0](https://github.com/pocket-id/pocket-id/commit/5a1c6f0547f779175107d31ea13d4f24000a1a65) by @stonith404) +- include SBOMs for binaries ([8fe53ed](https://github.com/pocket-id/pocket-id/commit/8fe53ed42c522717c8c69612cc700e195b8ab2ea) by @stonith404) +- replace local release script with action ([ba246e6](https://github.com/pocket-id/pocket-id/commit/ba246e61465378bc11571fcc77ca01c936fda174) by @stonith404) + +**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v2.15.0...v2.16.0 ## v2.15.0 ### Bug Fixes diff --git a/backend/internal/email/module.go b/backend/internal/email/module.go index 1f6b2230..38e318e9 100644 --- a/backend/internal/email/module.go +++ b/backend/internal/email/module.go @@ -36,6 +36,7 @@ type template[V any] struct { type templateData[V any] struct { AppName string LogoURL string + AppURL string Data *V } @@ -138,6 +139,7 @@ func send[V any](ctx context.Context, module *Module, dbConfig *appconfig.AppCon templateData := &templateData[V]{ AppName: dbConfig.AppName.String(), LogoURL: common.EnvConfig.AppURL + "/api/application-images/email", + AppURL: common.EnvConfig.AppURL, Data: data, } diff --git a/backend/internal/email/module_test.go b/backend/internal/email/module_test.go index 88afc793..c067ee80 100644 --- a/backend/internal/email/module_test.go +++ b/backend/internal/email/module_test.go @@ -67,7 +67,7 @@ func TestModuleSendsEveryEmailType(t *testing.T) { { name: "email verification", subject: "Verify your Pocket ID Test email address", - bodyContains: []string{"EMAIL VERIFICATION", "Hello Test User", "https://id.example.test/verify-token"}, + bodyContains: []string{"VERIFY YOUR EMAIL ADDRESS", "Hello Test User", "https://id.example.test/verify-token"}, send: func(ctx context.Context, config *appconfig.AppConfigModel) error { return module.SendEmailVerification(ctx, config, user.FullName(), userEmail, "https://id.example.test/verify-token") }, diff --git a/backend/internal/middleware/error_handler.go b/backend/internal/middleware/error_handler.go index fd1949d2..e5336fcf 100644 --- a/backend/internal/middleware/error_handler.go +++ b/backend/internal/middleware/error_handler.go @@ -194,6 +194,22 @@ func writeErrorResponse(c *gin.Context, classified classifiedError, requestID st func logRequestError(c *gin.Context, err error, classified classifiedError, requestID string) { if classified.status < http.StatusInternalServerError { + cause := errors.Unwrap(err) + if cause == nil { + return + } + + slog.DebugContext(c.Request.Context(), "Request rejected", + slog.String("error_code", string(classified.code)), + slog.String("error_type", errorTypeName(err)), + slog.String("cause_type", errorTypeName(cause)), + slog.Int("http_status", classified.status), + slog.String("request_id", requestID), + slog.String("http_method", c.Request.Method), + slog.String("http_path", c.Request.URL.Path), + slog.Any("error", err), + slog.Any("cause", cause), + ) return } diff --git a/backend/internal/webauthn/service.go b/backend/internal/webauthn/service.go index d05d70db..cdeedd05 100644 --- a/backend/internal/webauthn/service.go +++ b/backend/internal/webauthn/service.go @@ -281,6 +281,7 @@ func (s *Service) VerifyLogin(ctx context.Context, dbConfig *appconfig.AppConfig Extensions: storedSession.Extensions, CredParams: storedSession.CredentialParams, } + discardUnrequestedFalseAppIDOutput(session.Extensions, credentialAssertionData) var user *model.User _, err := s.webAuthn.ValidateDiscoverableLogin(func(_, userHandle []byte) (gowebauthn.User, error) { @@ -535,6 +536,7 @@ func (s *Service) CreateReauthenticationTokenWithWebauthn(ctx context.Context, s Extensions: storedSession.Extensions, CredParams: storedSession.CredentialParams, } + discardUnrequestedFalseAppIDOutput(session.Extensions, credentialAssertionData) // Validate the credential assertion var user *model.User @@ -589,6 +591,21 @@ func classifyPasskeyError(err error, fallback func(error) *apperror.Error) *appe return fallback(err) } +func discardUnrequestedFalseAppIDOutput(session protocol.SessionExtensions, credential *protocol.ParsedCredentialAssertionData) { + if credential == nil || credential.ClientExtensionResults.AppID == nil || *credential.ClientExtensionResults.AppID { + return + } + + for _, requested := range session.Requested { + if requested == protocol.ExtensionAppID { + return + } + } + + // Safari reports appid=false for security keys even when the relying party did not request the legacy extension + credential.ClientExtensionResults.AppID = nil +} + func (s *Service) ConsumeReauthenticationToken(ctx context.Context, tx *gorm.DB, token string, userID string) (time.Time, error) { hashedToken := utils.CreateSha256Hash(token) var reauthToken ReauthenticationToken diff --git a/backend/internal/webauthn/service_test.go b/backend/internal/webauthn/service_test.go index c270d1e3..283c210f 100644 --- a/backend/internal/webauthn/service_test.go +++ b/backend/internal/webauthn/service_test.go @@ -263,6 +263,66 @@ func TestClassifyPasskeyErrorPreservesStructuredLookupFailure(t *testing.T) { require.ErrorIs(t, err, cause) } +func TestDiscardUnrequestedFalseAppIDOutput(t *testing.T) { + tests := []struct { + name string + requested []string + appID bool + extra map[string]any + wantAppID *bool + wantError string + }{ + { + name: "unrequested false appid is discarded", + appID: false, + wantAppID: nil, + }, + { + name: "requested false appid is preserved", + requested: []string{protocol.ExtensionAppID}, + appID: false, + wantAppID: new(false), + }, + { + name: "unrequested true appid is rejected", + appID: true, + wantAppID: new(true), + wantError: "appid", + }, + { + name: "other unsolicited output is rejected", + appID: false, + extra: map[string]any{"example": true}, + wantAppID: nil, + wantError: "example", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + credential := &protocol.ParsedCredentialAssertionData{ + ParsedPublicKeyCredential: protocol.ParsedPublicKeyCredential{ + ClientExtensionResults: protocol.AuthenticationExtensionsClientOutputs{ + AppID: new(tc.appID), + Extra: tc.extra, + }, + }, + } + session := protocol.SessionExtensions{Requested: tc.requested} + + discardUnrequestedFalseAppIDOutput(session, credential) + + assert.Equal(t, tc.wantAppID, credential.ClientExtensionResults.AppID) + err := credential.ClientExtensionResults.Verify(session, protocol.AssertCeremony, protocol.UnsolicitedOutputPolicyReject) + if tc.wantError == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.wantError) + } + }) + } +} + func TestWebAuthnManagementOperationsReturnSpecificNotFoundErrors(t *testing.T) { service, err := newService(Dependencies{ DB: testutils.NewDatabaseForTest(t), diff --git a/backend/resources/email-templates/api-key-expiring-soon_html.tmpl b/backend/resources/email-templates/api-key-expiring-soon_html.tmpl index b9b3bb5c..451840c9 100644 --- a/backend/resources/email-templates/api-key-expiring-soon_html.tmpl +++ b/backend/resources/email-templates/api-key-expiring-soon_html.tmpl @@ -1 +1 @@ -{{define "root"}}
{{.AppName}}

{{.AppName}}

API Key Expiring Soon

Warning

Hello {{.Data.Name}},
This is a reminder that your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}.

Please generate a new API key if you need continued access.

{{end}} \ No newline at end of file +{{define "root"}}Your API key {{.Data.ApiKeyName}} expires on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}
Your API key {{.Data.ApiKeyName}} expires on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
{{.AppName}}{{.AppName}}

API key expiring soon

Hello {{.Data.Name}},

Your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}. Anything that uses this key will stop working once it expires.

To keep access, create a new API key in your {{.AppName}} account settings before then.

{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/api-key-expiring-soon_text.tmpl b/backend/resources/email-templates/api-key-expiring-soon_text.tmpl index 247969d5..fb04995e 100644 --- a/backend/resources/email-templates/api-key-expiring-soon_text.tmpl +++ b/backend/resources/email-templates/api-key-expiring-soon_text.tmpl @@ -1,11 +1,7 @@ -{{define "root"}}{{.AppName}} - - -API KEY EXPIRING SOON - -Warning +{{define "root"}}API KEY EXPIRING SOON Hello {{.Data.Name}}, -This is a reminder that your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}. -Please generate a new API key if you need continued access.{{end}} \ No newline at end of file +Your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}. Anything that uses this key will stop working once it expires. + +To keep access, create a new API key in your {{.AppName}} account settings before then.{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/email-verification_html.tmpl b/backend/resources/email-templates/email-verification_html.tmpl index 3d32f78f..bd98dc3f 100644 --- a/backend/resources/email-templates/email-verification_html.tmpl +++ b/backend/resources/email-templates/email-verification_html.tmpl @@ -1 +1 @@ -{{define "root"}}
{{.AppName}}

{{.AppName}}

Email Verification

Hello {{.Data.UserFullName}},
Click the button below to verify your email address for {{.AppName}}. This link will expire in 24 hours.

{{end}} \ No newline at end of file +{{define "root"}}Confirm the email address for your {{.AppName}} account
Confirm the email address for your {{.AppName}} account
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
{{.AppName}}{{.AppName}}

Verify your email address

Hello {{.Data.UserFullName}},

Click the button below to confirm the email address for your {{.AppName}} account. This link expires in 24 hours.

Verify email address

Or if you don't like clicking buttons, open this link:
{{.Data.VerificationLink}}

{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/email-verification_text.tmpl b/backend/resources/email-templates/email-verification_text.tmpl index 660394ed..fdaef652 100644 --- a/backend/resources/email-templates/email-verification_text.tmpl +++ b/backend/resources/email-templates/email-verification_text.tmpl @@ -1,10 +1,10 @@ -{{define "root"}}{{.AppName}} - - -EMAIL VERIFICATION +{{define "root"}}VERIFY YOUR EMAIL ADDRESS Hello {{.Data.UserFullName}}, -Click the button below to verify your email address for {{.AppName}}. This link will expire in 24 hours. +Click the button below to confirm the email address for your {{.AppName}} account. This link expires in 24 hours. -Verify {{.Data.VerificationLink}}{{end}} \ No newline at end of file +Verify email address {{.Data.VerificationLink}} + +Or if you don't like clicking buttons, open this link: +{{.Data.VerificationLink}}{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/login-with-new-device_html.tmpl b/backend/resources/email-templates/login-with-new-device_html.tmpl index a3c5aa5c..06211b1b 100644 --- a/backend/resources/email-templates/login-with-new-device_html.tmpl +++ b/backend/resources/email-templates/login-with-new-device_html.tmpl @@ -1 +1 @@ -{{define "root"}}
{{.AppName}}

{{.AppName}}

New Sign-In Detected

Warning

Your {{.AppName}} account was recently accessed from a new IP address or browser. If you recognize this activity, no further action is required.

Details

Approximate Location

{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}

IP Address

{{.Data.IPAddress}}

Device

{{.Data.Device}}

Sign-In Time

{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}

{{end}} \ No newline at end of file +{{define "root"}}A new sign-in to your {{.AppName}} account was detected
A new sign-in to your {{.AppName}} account was detected
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
{{.AppName}}{{.AppName}}

New sign-in detected

Your {{.AppName}} account was recently accessed from a new IP address or browser. If this was you, no further action is needed.

Approximate location
{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}
IP address
{{.Data.IPAddress}}
Device
{{.Data.Device}}
Time
{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}

If you don't recognize this activity, review the passkeys in your {{.AppName}} account settings and remove any you don't recognize.

{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/login-with-new-device_text.tmpl b/backend/resources/email-templates/login-with-new-device_text.tmpl index 9d8c183c..e858802a 100644 --- a/backend/resources/email-templates/login-with-new-device_text.tmpl +++ b/backend/resources/email-templates/login-with-new-device_text.tmpl @@ -1,26 +1,17 @@ -{{define "root"}}{{.AppName}} +{{define "root"}}NEW SIGN-IN DETECTED +Your {{.AppName}} account was recently accessed from a new IP address or browser. If this was you, no further action is needed. -NEW SIGN-IN DETECTED - -Warning - -Your {{.AppName}} account was recently accessed from a new IP address or browser. If you recognize this activity, no further action is required. - -DETAILS - -Approximate Location - +Approximate location {{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}} -IP Address - +IP address {{.Data.IPAddress}} Device - {{.Data.Device}} -Sign-In Time +Time +{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}} -{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}{{end}} \ No newline at end of file +If you don't recognize this activity, review the passkeys in your {{.AppName}} account settings and remove any you don't recognize.{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/one-time-access_html.tmpl b/backend/resources/email-templates/one-time-access_html.tmpl index 86007b1c..c84b26bd 100644 --- a/backend/resources/email-templates/one-time-access_html.tmpl +++ b/backend/resources/email-templates/one-time-access_html.tmpl @@ -1 +1 @@ -{{define "root"}}
{{.AppName}}

{{.AppName}}

Your Login Code

Click the button below to sign in to {{.AppName}} with a login code.
Or visit {{.Data.LoginLink}} and enter the code {{.Data.Code}}.

This code expires in {{.Data.ExpirationString}}.

{{end}} \ No newline at end of file +{{define "root"}}Your {{.AppName}} login code is {{.Data.Code}}
Your {{.AppName}} login code is {{.Data.Code}}
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
{{.AppName}}{{.AppName}}

Your login code

Use the code below to sign in to {{.AppName}}. It expires in {{.Data.ExpirationString}}.

{{.Data.Code}}

Sign in

Or open {{.Data.LoginLink}} and enter the code manually.

{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/one-time-access_text.tmpl b/backend/resources/email-templates/one-time-access_text.tmpl index 8a311977..040da20a 100644 --- a/backend/resources/email-templates/one-time-access_text.tmpl +++ b/backend/resources/email-templates/one-time-access_text.tmpl @@ -1,11 +1,9 @@ -{{define "root"}}{{.AppName}} +{{define "root"}}YOUR LOGIN CODE +Use the code below to sign in to {{.AppName}}. It expires in {{.Data.ExpirationString}}. -YOUR LOGIN CODE +{{.Data.Code}} -Click the button below to sign in to {{.AppName}} with a login code. -Or visit {{.Data.LoginLink}} and enter the code {{.Data.Code}}. +Sign in {{.Data.LoginLinkWithCode}} -This code expires in {{.Data.ExpirationString}}. - -Sign In {{.Data.LoginLinkWithCode}}{{end}} \ No newline at end of file +Or open {{.Data.LoginLink}} and enter the code manually.{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/test_html.tmpl b/backend/resources/email-templates/test_html.tmpl index ac1b65be..985c5981 100644 --- a/backend/resources/email-templates/test_html.tmpl +++ b/backend/resources/email-templates/test_html.tmpl @@ -1 +1 @@ -{{define "root"}}
{{.AppName}}

{{.AppName}}

Test Email

Your email setup is working correctly!

{{end}} \ No newline at end of file +{{define "root"}}Your email setup is working correctly
Your email setup is working correctly
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
{{.AppName}}{{.AppName}}

Test email

Your email setup is working correctly!

{{end}} \ No newline at end of file diff --git a/backend/resources/email-templates/test_text.tmpl b/backend/resources/email-templates/test_text.tmpl index 08459a50..9da30fc4 100644 --- a/backend/resources/email-templates/test_text.tmpl +++ b/backend/resources/email-templates/test_text.tmpl @@ -1,6 +1,3 @@ -{{define "root"}}{{.AppName}} - - -TEST EMAIL +{{define "root"}}TEST EMAIL Your email setup is working correctly!{{end}} \ No newline at end of file diff --git a/docker/Dockerfile-distroless b/docker/Dockerfile-distroless index d252de3c..ca3819cd 100644 --- a/docker/Dockerfile-distroless +++ b/docker/Dockerfile-distroless @@ -10,6 +10,7 @@ ARG TARGETVARIANT WORKDIR /app COPY linux/${TARGETARCH}/${TARGETVARIANT}/pocket-id /app/pocket-id +COPY ./.tmp/frontend.cdx.json /usr/share/doc/pocket-id/frontend.cdx.json EXPOSE 1411 ENV APP_ENV=production diff --git a/docker/Dockerfile-prebuilt b/docker/Dockerfile-prebuilt index 156c8ab2..0be7185e 100644 --- a/docker/Dockerfile-prebuilt +++ b/docker/Dockerfile-prebuilt @@ -13,6 +13,7 @@ RUN apk add --no-cache su-exec COPY linux/${TARGETARCH}/${TARGETVARIANT}/pocket-id /app/pocket-id COPY ./scripts/docker /app/docker +COPY ./.tmp/frontend.cdx.json /usr/share/doc/pocket-id/frontend.cdx.json EXPOSE 1411 ENV APP_ENV=production diff --git a/email-templates/build.ts b/email-templates/build.ts index 3c235918..3e407a37 100644 --- a/email-templates/build.ts +++ b/email-templates/build.ts @@ -1,4 +1,4 @@ -import { render } from "@react-email/components"; +import { render } from "react-email"; import * as fs from "node:fs"; import * as path from "node:path"; @@ -17,9 +17,10 @@ async function buildTemplateFile( templateName: string, isPlainText: boolean ) { - const rendered = await render(Component(Component.TemplateProps), { - plainText: isPlainText, - }); + const rendered = await render( + Component(Component.TemplateProps), + isPlainText ? { plainText: true } : {}, + ); // Normalize quotes const normalized = rendered.replace(/"/g, '"'); diff --git a/email-templates/components/base-template.tsx b/email-templates/components/base-template.tsx index c4e7590d..5f027b3d 100644 --- a/email-templates/components/base-template.tsx +++ b/email-templates/components/base-template.tsx @@ -5,77 +5,115 @@ import { Head, Html, Img, + Link, + Preview, Row, Section, - Text, -} from "@react-email/components"; +} from "react-email"; +import type { SharedProps } from "../props"; +import { colors, fonts, radius } from "./theme"; -interface BaseTemplateProps { - logoURL?: string; - appName: string; +interface BaseTemplateProps extends SharedProps { + preview?: string; children: React.ReactNode; } export const BaseTemplate = ({ logoURL, appName, + appURL, + preview, children, -}: BaseTemplateProps) => { - return ( - - - - -
- - +}: BaseTemplateProps) => ( + + + + +