Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions .github/workflows/handbook-build-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,20 @@ on:
- "scripts/assemble-handbook-screenshots.sh"
- "scripts/assemble-handbook-store-listing.py"
- "scripts/templates/store-listing.html.tmpl"
- "scripts/assemble-handbook-legal.py"
- "scripts/build-legal-downloads.sh"
- "scripts/templates/legal-downloads.html.tmpl"
- "test/goldens/**"
# Store-listing section is a derived export of the Fastlane metadata —
# changing it must re-run the generator and re-commit the handbook.
- "ios/fastlane/metadata/**"
- "ios/fastlane/screenshots/**"
- "android/fastlane/metadata/**"
# Legal-downloads section is a derived export of the in-app legal Markdown
# (and the ARB titles) — changing either must re-run the generator and
# re-commit the handbook HTML block.
- "assets/legal/**"
- "assets/languages/**"
- "Dockerfile.handbook"
- "handbook.nginx.conf"
- "handbook.htpasswd"
Expand Down Expand Up @@ -99,6 +107,23 @@ jobs:
exit 1
fi

- name: Verify handbook legal-downloads section is in sync with assets/legal
# The legal-downloads block in docs/handbook/de/index.html is a derived
# export of assets/legal/*.md + the ARB titles. Re-run the (pure-stdlib,
# deterministic) generator; if the working tree changes, the committed
# handbook is stale — someone edited a legal .md or its ARB title without
# re-running the generator. Only the HTML block is gated here; the
# pandoc-produced PDF/DOCX are non-deterministic and intentionally
# git-ignored, so build-legal-downloads.sh is NOT run in this gate.
run: |
set -euo pipefail
python3 scripts/assemble-handbook-legal.py /tmp/legal-out
if ! git diff --quiet docs/handbook/de/index.html; then
echo "::error::docs/handbook/de/index.html legal-downloads block is stale — re-run scripts/assemble-handbook-legal.py and commit."
git diff docs/handbook/de/index.html
exit 1
fi

- name: Build handbook image (no push)
run: docker build -f Dockerfile.handbook -t realunit-handbook:pr-check .

Expand Down Expand Up @@ -139,4 +164,16 @@ jobs:
fi
done

# Legal downloads must be present (built by the legal-docs-builder
# stage via pandoc). Same 200/401-vs-404 logic: 404 means the PDF/DOCX
# was not produced. Covers PDF + DOCX and both languages.
for f in legal/privacy_policy_de.pdf legal/privacy_policy_de.docx legal/terms_of_use_en.pdf legal/registration_agreement_de.docx; do
code=$(curl -s -o /dev/null -w '%{http_code}' -u "${HANDBOOK_USER:-x}:${HANDBOOK_PASS:-x}" "http://127.0.0.1:8080/${f}")
if [ "$code" = "404" ]; then
echo "legal download ${f} missing from /usr/share/nginx/html/legal/" >&2
docker logs handbook
exit 1
fi
done

docker stop handbook
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ docs/handbook/mails/
# directory is only populated transiently for local previews.
docs/handbook/screenshots/

# Built at handbook build time from assets/legal/*.md via pandoc
# (scripts/build-legal-downloads.sh, see Dockerfile.handbook). The PDF/DOCX are
# NON-deterministic (pandoc embeds timestamps/metadata), so — unlike the
# committed legal-downloads HTML block in de/index.html — they are never
# committed and never sync-gated; they exist only inside the image. This
# directory holds the assembly output for local previews only.
docs/handbook/legal/

# Scratch directories produced when reproducing the handbook CI's
# mail-preview generation locally (see docs/handbook/README.md → "E-Mail
# Previews → Lokal regenerieren"). Mirrors the names the CI uses so a
Expand Down
31 changes: 31 additions & 0 deletions Dockerfile.handbook
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ COPY android/fastlane/metadata/ ./android/fastlane/metadata/
COPY docs/handbook/de/index.html ./docs/handbook/de/index.html
RUN python3 ./scripts/assemble-handbook-store-listing.py /out && cp ./docs/handbook/de/index.html /out/index.html

# Legal-downloads section: derived export of the in-app legal Markdown
# (assets/legal/*.md). Two separate concerns, by design:
# - assemble-handbook-legal.py rewrites the deterministic <!-- BEGIN/END:
# legal-downloads --> block in index.html (committed + sync-gated upstream).
# - build-legal-downloads.sh renders the NON-deterministic PDF/DOCX via pandoc
# (weasyprint PDF engine — no TeX), git-ignored like the screenshots.
# This stage takes the store-listing-rewritten index.html as input so BOTH the
# store-listing and the legal-downloads blocks survive into the final image.
FROM alpine:3.20 AS legal-docs-builder
WORKDIR /work
# font-dejavu (+ a rebuilt fontconfig cache) is required: weasyprint renders via
# Pango, which aborts ("No fonts configured in FontConfig" → "Error producing
# PDF") on a bare Alpine that ships no font files. DejaVu covers the Latin/German
# glyphs the legal texts use.
RUN apk add --no-cache python3 pandoc weasyprint bash font-dejavu \
&& fc-cache -f
COPY scripts/assemble-handbook-legal.py ./scripts/
COPY scripts/build-legal-downloads.sh ./scripts/
COPY scripts/templates/legal-downloads.html.tmpl ./scripts/templates/
COPY assets/legal/ ./assets/legal/
COPY assets/languages/ ./assets/languages/
COPY --from=store-listing-builder /out/index.html ./docs/handbook/de/index.html
RUN python3 ./scripts/assemble-handbook-legal.py /out \
&& bash ./scripts/build-legal-downloads.sh /out \
&& cp ./docs/handbook/de/index.html /out/index.html

FROM nginx:1.27.5-alpine

COPY docs/handbook/ /usr/share/nginx/html/
Expand All @@ -62,6 +88,11 @@ COPY --from=screenshots-builder /out/ /usr/share/nginx/html/screenshots/
COPY --from=store-listing-builder /out/ios/ /usr/share/nginx/html/store/ios/
COPY --from=store-listing-builder /out/android/ /usr/share/nginx/html/store/android/
COPY --from=store-listing-builder /out/index.html /usr/share/nginx/html/de/index.html
# Legal-downloads PDFs/DOCX + the index.html with BOTH the store-listing and the
# legal-downloads blocks rewritten (this copy overwrites the store-listing one
# above, so the legal stage's index.html is the authoritative final version).
COPY --from=legal-docs-builder /out/legal/ /usr/share/nginx/html/legal/
COPY --from=legal-docs-builder /out/index.html /usr/share/nginx/html/de/index.html
COPY handbook.nginx.conf /etc/nginx/conf.d/default.conf
COPY handbook.htpasswd /etc/nginx/handbook.htpasswd

Expand Down
4 changes: 3 additions & 1 deletion assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"buyExecutedReference": "Ihre Referenz",
"buyExecutedTitle": "Vielen Dank.",
"buyMinAmount": "Mindestbetrag: ${amount} ${currency}",
"buyPaymentConfirm": "Klicken Sie hier, sobald Sie die Überweisung getätigt haben",
"buyPaymentConfirm": "Zahlungsanweisungen per E-Mail anfordern",
"buyPaymentConfirmFailed": "Es gibt ein technisches Problem. Bitte versuchen Sie es später erneut. Falls der Fehler weiterhin besteht, kontaktieren Sie unseren Support.",
"buyPaymentConfirmFailedAktionariat": "Es gibt ein technisches Problem. Bitte überprüfen Sie Ihr E-Mail-Postfach, möglicherweise fehlt noch eine Bestätigung Ihrer Blockchain-Adresse. Andernfalls versuchen Sie es später erneut. Falls der Fehler weiterhin besteht, kontaktieren Sie unseren Support.",
"buyPaymentInformation": "Zahlungsinformationen",
Expand Down Expand Up @@ -148,6 +148,8 @@
"legalDisclaimerTitle": "Wichtige rechtliche Hinweise für Investoren & Bestätigung des Wohnsitzes",
"legalDisclaimerTitle2": "Weitere rechtliche Hinweise",
"legalDisclaimerYes": "Zustimmen",
"legalDocumentLoadFailed": "Dokument konnte nicht geladen werden",
"legalDocumentLoadFailedDescription": "Beim Laden des Dokuments ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"legalDocuments": "Rechtsdokumente",
"location": "Ort",
"logout": "Abmelden",
Expand Down
4 changes: 3 additions & 1 deletion assets/languages/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"buyExecutedReference": "Your reference",
"buyExecutedTitle": "Thank you.",
"buyMinAmount": "Minimum amount: ${amount} ${currency}",
"buyPaymentConfirm": "Click here once you have made the transfer",
"buyPaymentConfirm": "Request payment instructions by email",
"buyPaymentConfirmFailed": "There is a technical problem. Please try again later. If the error persists, contact our support team.",
"buyPaymentConfirmFailedAktionariat": "There is a technical problem. Please check your email inbox — you may still need to confirm your blockchain address. Otherwise, please try again later. If the error persists, contact our support team.",
"buyPaymentInformation": "Payment information",
Expand Down Expand Up @@ -148,6 +148,8 @@
"legalDisclaimerTitle": "Important legal notices for investors & confirmation of residence",
"legalDisclaimerTitle2": "Further legal notices",
"legalDisclaimerYes": "Agree",
"legalDocumentLoadFailed": "Could not load the document",
"legalDocumentLoadFailedDescription": "Something went wrong while loading this document. Please try again.",
"legalDocuments": "Legal documents",
"location": "Location",
"logout": "Logout",
Expand Down
157 changes: 157 additions & 0 deletions docs/handbook/de/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,40 @@
.store-screenshots .src {
font-size: 0.85em;
}
/* Legal-downloads section (#spec-legal-downloads) — see
scripts/assemble-handbook-legal.py. Reuses the .test card chrome
(border + :target highlight) so each (document, language) entry has the
same direct-link affordance as the screenshot cards. */
.test.legal-doc .downloads {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
padding: 12px 14px;
}
.test.legal-doc .doc-title {
flex-basis: 100%;
font-size: 13px;
color: var(--ink-2);
}
a.dl-btn {
text-decoration: none;
font-size: 12.5px;
font-weight: 600;
color: var(--brand);
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: 6px;
padding: 5px 12px;
transition:
background 0.15s,
border-color 0.15s,
color 0.15s;
}
a.dl-btn:hover {
background: var(--surface);
border-color: var(--brand);
}
</style>
</head>
<body>
Expand Down Expand Up @@ -689,6 +723,9 @@
<li>
<a href="#spec-store-listing"><span class="spec-num">S</span>Store-Listing</a>
</li>
<li>
<a href="#spec-legal-downloads"><span class="spec-num">L</span>Rechtsdokumente</a>
</li>
</ol>
</nav>
</aside>
Expand Down Expand Up @@ -2411,6 +2448,126 @@ <h4>10" Tablet (2560×1440)</h4>
</div>
</details>
<!-- END:store-listing -->

<!-- BEGIN:legal-downloads -->
<details id="spec-legal-downloads" class="spec" open>
<summary>
<div class="spec-head">
<div class="lhs">
<h2><span class="num">L</span>Rechtsdokumente — Downloads</h2>
<div class="file">assets/legal/*.md</div>
</div>
<div class="rhs">
<b>Live aus Repo</b>
<div class="links">
<a href="https://github.com/RealUnitCH/app/tree/develop/assets/legal">assets/legal ↗</a>
</div>
</div>
</div>
</summary>

<p class="spec-intro">
Single source of truth für die App-eigenen Rechtstexte sind die Markdown-Dateien
unter <code>assets/legal/*.md</code>. Die In-App-Ansicht (<code>LegalDocumentPage</code>)
und die Downloads hier rendern aus denselben Dateien; jede Änderung am Text geht
ausschliesslich über einen PR auf diese Markdown-Quellen. Pro Dokument und Sprache
gibt es einen <b>PDF</b>- und <b>DOCX</b>-Download (im Handbook-Image via
<code>pandoc</code> erzeugt) sowie einen Direkt-Link (🔗) auf den jeweiligen Eintrag
und einen <span class="src">↗</span>-Link auf die Markdown-Quelldatei.
DFX- und Aktionariat-Dokumente sowie die extern gehosteten Unternehmensdokumente
(Prospekte, Statuten) sind <em>nicht</em> Teil dieses Exports — sie haben keine
Markdown-Quelle im Repo.
</p>

<h3>Datenschutzbestimmungen <a class="src" href="https://github.com/RealUnitCH/app/tree/develop/assets/legal" title="Quelle: assets/legal/">↗</a></h3>
<div class="tests cols-2">
<div class="test legal-doc" id="legal-privacy_policy-de">
<div class="head">
<a class="name permalink" href="#legal-privacy_policy-de">privacy_policy_de</a>
<button class="copy-link" type="button" data-target="legal-privacy_policy-de" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">de</span>
</div>
<div class="downloads">
<span class="doc-title">Datenschutzbestimmungen</span>
<a class="dl-btn" href="../legal/privacy_policy_de.pdf" download>PDF</a>
<a class="dl-btn" href="../legal/privacy_policy_de.docx" download>DOCX</a>
<a class="src" href="https://github.com/RealUnitCH/app/blob/develop/assets/legal/privacy_policy_de.md" title="Quelle: privacy_policy_de.md">Markdown ↗</a>
</div>
</div>
<div class="test legal-doc" id="legal-privacy_policy-en">
<div class="head">
<a class="name permalink" href="#legal-privacy_policy-en">privacy_policy_en</a>
<button class="copy-link" type="button" data-target="legal-privacy_policy-en" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">en</span>
</div>
<div class="downloads">
<span class="doc-title">Privacy Policy</span>
<a class="dl-btn" href="../legal/privacy_policy_en.pdf" download>PDF</a>
<a class="dl-btn" href="../legal/privacy_policy_en.docx" download>DOCX</a>
<a class="src" href="https://github.com/RealUnitCH/app/blob/develop/assets/legal/privacy_policy_en.md" title="Quelle: privacy_policy_en.md">Markdown ↗</a>
</div>
</div>
</div>
<h3>Nutzungsbedingungen <a class="src" href="https://github.com/RealUnitCH/app/tree/develop/assets/legal" title="Quelle: assets/legal/">↗</a></h3>
<div class="tests cols-2">
<div class="test legal-doc" id="legal-terms_of_use-de">
<div class="head">
<a class="name permalink" href="#legal-terms_of_use-de">terms_of_use_de</a>
<button class="copy-link" type="button" data-target="legal-terms_of_use-de" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">de</span>
</div>
<div class="downloads">
<span class="doc-title">Nutzungsbedingungen</span>
<a class="dl-btn" href="../legal/terms_of_use_de.pdf" download>PDF</a>
<a class="dl-btn" href="../legal/terms_of_use_de.docx" download>DOCX</a>
<a class="src" href="https://github.com/RealUnitCH/app/blob/develop/assets/legal/terms_of_use_de.md" title="Quelle: terms_of_use_de.md">Markdown ↗</a>
</div>
</div>
<div class="test legal-doc" id="legal-terms_of_use-en">
<div class="head">
<a class="name permalink" href="#legal-terms_of_use-en">terms_of_use_en</a>
<button class="copy-link" type="button" data-target="legal-terms_of_use-en" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">en</span>
</div>
<div class="downloads">
<span class="doc-title">Terms of use</span>
<a class="dl-btn" href="../legal/terms_of_use_en.pdf" download>PDF</a>
<a class="dl-btn" href="../legal/terms_of_use_en.docx" download>DOCX</a>
<a class="src" href="https://github.com/RealUnitCH/app/blob/develop/assets/legal/terms_of_use_en.md" title="Quelle: terms_of_use_en.md">Markdown ↗</a>
</div>
</div>
</div>
<h3>Registrierungsvereinbarung <a class="src" href="https://github.com/RealUnitCH/app/tree/develop/assets/legal" title="Quelle: assets/legal/">↗</a></h3>
<div class="tests cols-2">
<div class="test legal-doc" id="legal-registration_agreement-de">
<div class="head">
<a class="name permalink" href="#legal-registration_agreement-de">registration_agreement_de</a>
<button class="copy-link" type="button" data-target="legal-registration_agreement-de" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">de</span>
</div>
<div class="downloads">
<span class="doc-title">Registrierungsvereinbarung</span>
<a class="dl-btn" href="../legal/registration_agreement_de.pdf" download>PDF</a>
<a class="dl-btn" href="../legal/registration_agreement_de.docx" download>DOCX</a>
<a class="src" href="https://github.com/RealUnitCH/app/blob/develop/assets/legal/registration_agreement_de.md" title="Quelle: registration_agreement_de.md">Markdown ↗</a>
</div>
</div>
<div class="test legal-doc" id="legal-registration_agreement-en">
<div class="head">
<a class="name permalink" href="#legal-registration_agreement-en">registration_agreement_en</a>
<button class="copy-link" type="button" data-target="legal-registration_agreement-en" title="Direkt-Link kopieren" aria-label="Direkt-Link kopieren">🔗 Link</button>
<span class="src">en</span>
</div>
<div class="downloads">
<span class="doc-title">Registration Agreement</span>
<a class="dl-btn" href="../legal/registration_agreement_en.pdf" download>PDF</a>
<a class="dl-btn" href="../legal/registration_agreement_en.docx" download>DOCX</a>
<a class="src" href="https://github.com/RealUnitCH/app/blob/develop/assets/legal/registration_agreement_en.md" title="Quelle: registration_agreement_en.md">Markdown ↗</a>
</div>
</div>
</div>
</details>
<!-- END:legal-downloads -->
</main>
</div>
</body>
Expand Down
16 changes: 16 additions & 0 deletions handbook.nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ server {
return 302 /de/;
}

# Legal downloads (/legal/*.pdf, /legal/*.docx) — the derived PDF/DOCX export
# of assets/legal/*.md. Force a download disposition (nginx's default
# mime.types has no .docx mapping; it would otherwise serve as
# application/octet-stream, which downloads but without an explicit
# filename). add_header is all-or-nothing per location, so the server-level
# security headers are restated here. auth_basic is inherited — these stay
# behind the same Basic-Auth gate as the rest of the handbook.
location ~* \.(pdf|docx)$ {
add_header Content-Disposition "attachment" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Cache-Control "private, no-store" always;
try_files $uri =404;
}

location / {
try_files $uri $uri/ =404;
}
Expand Down
8 changes: 8 additions & 0 deletions lib/packages/repository/wallet_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ class WalletRepository {

Future<void> deleteWallet(int id) => _appDatabase.deleteWallet(id);

/// Full purge for the user-facing delete: removes the encrypted-seed row AND
/// the AES-GCM mnemonic key, so no recoverable seed material remains on
/// device.
Future<void> purgeWallet(int id) async {
await _appDatabase.deleteWalletCompletely(id);
await _secureStorage.deleteMnemonicKey();
}

Future<WalletInfo> _decryptWalletInfo(WalletInfo info) async {
final key = await _secureStorage.getOrCreateMnemonicKey();
final decryptedSeed = SecureStorage.decryptSeed(key, info.seed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ class RealUnitSellPaymentInfoService extends DFXAuthService {
chainId: paymentInfo.eip7702.domain.chainId,
address: paymentInfo.eip7702.delegatorAddress,
nonce: paymentInfo.eip7702.userNonce,
r: '0x${authorizationSignature.r.toRadixString(16)}',
s: '0x${authorizationSignature.s.toRadixString(16)}',
r: '0x${authorizationSignature.r.toRadixString(16).padLeft(64, '0')}',
s: '0x${authorizationSignature.s.toRadixString(16).padLeft(64, '0')}',
yParity: authorizationSignature.yParity,
),
),
Expand Down
Loading
Loading