diff --git a/.github/sync.yml b/.github/sync.yml new file mode 100644 index 00000000..9793455b --- /dev/null +++ b/.github/sync.yml @@ -0,0 +1,8 @@ +Tiqr/eduid-app-android: + - source: localizations.yaml + dest: localizations.yaml + +Tiqr/eduid-app-ios: + - source: localizations.yaml + dest: EduID/localizations.yaml + diff --git a/.github/workflows/localicious.yaml b/.github/workflows/localicious.yaml new file mode 100644 index 00000000..c4d8fd58 --- /dev/null +++ b/.github/workflows/localicious.yaml @@ -0,0 +1,59 @@ +name: Update translations +on: + workflow_dispatch: + push: + paths: + - 'localizations.yaml' +jobs: + localicious: + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + - name: lint yaml files + uses: ibiqlik/action-yamllint@v3 + with: + file_or_dir: ./localizations.yaml + - uses: actions/setup-node@v4 + with: + cache-dependency-path: myconext-gui/ + node-version: 16 + cache: 'npm' + - name: Install localicious/ + run: | + npm install -g @picnicsupermarket/localicious + - name: Create Localizable.strings files + run: | + cd ${{ github.workspace }} + localicious render ./localizations.yaml ./account-gui/src/locale/ --languages en,nl --outputTypes js -c SHARED + rm -fr ./account-gui/src/locale/js/Localizable.ts + localicious render ./localizations.yaml ./myconext-gui/src/locale/ --languages en,nl --outputTypes js -c SHARED + rm -fr ./myconext-gui/src/locale/js/Localizable.ts + - name: Commit updated files + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Automated update of strings.xml after updating localizations.yaml + file_pattern: '**/strings.json' + sync-eduid-apps: + needs: localicious + runs-on: ubuntu-24.04 + if: ${{ !contains(github.event.head_commit.message, '#AUTO#') }} + steps: + - name: Checkout Repository + uses: actions/checkout@master + - name: Get token for the Tiqr github org + uses: actions/create-github-app-token@v1 + id: app-token-tiqr-org + with: + app-id: ${{ secrets.SYNC_APP_ID }} + private-key: ${{ secrets.SYNC_PRIVATE_KEY }} + owner: Tiqr + - name: Create PR for new translation in eduid-app repos + uses: BetaHuhn/repo-file-sync-action@v1 + with: + GH_INSTALLATION_TOKEN: ${{ steps.app-token-tiqr-org.outputs.token }} + COMMIT_PREFIX: "#AUTO#" + CONFIG_PATH: .github/sync.yml + + \ No newline at end of file diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 00000000..0db1ba9d --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,34 @@ +--- + +yaml-files: + - '*.yaml' + - '*.yml' + - '.yamllint' + +rules: + anchors: enable + braces: enable + brackets: enable + colons: enable + commas: enable + comments: + level: warning + comments-indentation: + level: warning + document-end: disable + document-start: + level: warning + empty-lines: enable + empty-values: disable + float-values: disable + hyphens: enable + indentation: enable + key-duplicates: enable + key-ordering: disable + line-length: disable + new-line-at-end-of-file: enable + new-lines: enable + octal-values: disable + quoted-strings: disable + trailing-spaces: enable + truthy: disable diff --git a/README.md b/README.md index 87560a3a..e0f0d26f 100644 --- a/README.md +++ b/README.md @@ -5,31 +5,52 @@ An IdP for OpenConext. A user can create and manage his own identity. Authentication uses a magic-link by default, and FIDO2 or a password can be added later. -## [Getting started](#getting-started) - -### [System Requirements](#system-requirements) - -- Java 11 +## Content + +- [Getting started](#getting-started) + - [System Requirements](#system-requirements) +- [Building and running](#building-and-running) + - [The myconext-server](#The-myconext-server) + - [The account-gui](#the-account-gui) + - [The myconext-gui](#The-myconext-gui) + - [Build](#build) + - [Mail](#mail) + - [Crypto](#crypto) + - [Miscellaneous](#miscellaneous) + - [Migration](#migration) + - [Attribute Manipulation](#attribute-manipulation) + - [Attribute Aggregation](#attribute-aggregation) + - [OpenAPI Documentation](#OpenAPI-Documentation) + - [IDIN & e-Herkenning](#IDIN-&-e-Herkenning) + - [Running the IdP and testing localhost](#Running-the-IdP-and-testing-localhost) + + +## Getting started + +### System Requirements + +- Java 21 - Maven 3 - MongoDB 3.4.x - Yarn 1.x - NodeJS -- Ansible +- (Ansible) -## [Building and running](#building-and-running) +## Building and running -### [The myconext-server](#myconext-server) +### The myconext-server This project uses Spring Boot and Maven. To run locally, type: -`cd myconext-server` - -`mvn spring-boot:run -Dspring-boot.run.profiles=dev` +``` +cd myconext-server +mvn spring-boot:run -Dspring-boot.run.profiles=dev +``` When developing, it's convenient to just execute the applications main-method, which is in [Application](myconext-server/src/main/java/myconext/MyConextServerApplication.java). Don't forget to set the active profile to dev. -### [The account-gui](#myconext-gui) +### The account-gui The myconext client is build with Svelte and to get initially started: @@ -41,7 +62,7 @@ yarn dev Browse to the [application homepage](http://localhost:3001/). -### [The myconext-gui](#myconext-gui) +### The myconext-gui The IdP is also build with Svelte and to get initially started: @@ -53,13 +74,13 @@ yarn start There is no home page, you'll need to visit an SP and choose eduID to login. -### [Build](#build) +### Build To deploy production bundles ```bash mvn deploy ``` -### [Mail](#mail) +### Mail The default mail configuration sends mails to port 1025. Install https://mailpit.axllent.org/ and capture all emails send. You can see all mails delivered at http://0.0.0.0:8025/ when mailpit is installed. @@ -67,7 +88,7 @@ You can see all mails delivered at http://0.0.0.0:8025/ when mailpit is installe brew install mailpit ``` -### [Crypto](#crypto) +### Crypto The myconext application uses a private RSA key and corresponding certificate to sign the SAML requests. We don't want to provide defaults, so in the integration tests the key / certificate pair is generated on the fly. if you want to @@ -87,23 +108,34 @@ If you need to register the public key in EB then issue this command and copy & ``` cat myconext.crt |ghead -n -1 |tail -n +2 | tr -d '\n'; echo ``` -### [Miscellaneous](#miscellaneous) +### Translations + +The github actions will generate new translations of the source is changed. + +``` +yarn localicious render ./localizations.yaml ./account-gui/src/locale/ --languages en,nl --outputTypes js -c SHARED +rm -fr ./account-gui/src/locale/js/Localizable.ts +yarn localicious render ./localizations.yaml ./myconext-gui/src/locale/ --languages en,nl --outputTypes js -c SHARED +rm -fr ./myconext-gui/src/locale/js/Localizable.ts +``` + +### Miscellaneous To get an overview of the git source file's: ``` cloc --read-lang-def=cloc_definitions.txt --vcs=git ``` -### [Migration](#migration) +### Migration It's possible to migrate from an existing IdP to this IdP. A new identity will be created, and the eppn wil be copied. -### [Attribute Manipulation](#attribute-manipulation) +### Attribute Manipulation ``` curl -u oidcng:secret "http://login.test2.eduid.nl/myconext/api/attribute-manipulation?sp_entity_id=https://test.okke&uid=0eaa7fb2-4f94-476f-b3f6-c8dfc4115a87&sp_institution_guid=null" ``` -### [Attribute Aggregation](#attribute-aggregation) +### Attribute Aggregation ``` curl -u aa:secret "https://login.test2.eduid.nl/myconext/api/attribute-aggregation?sp_entity_id=https://mijn.test2.eduid.nl/shibboleth&eduperson_principal_name=j.doe@example.com" ``` diff --git a/account-gui/package.json b/account-gui/package.json index 29a19601..59d37e49 100644 --- a/account-gui/package.json +++ b/account-gui/package.json @@ -38,7 +38,9 @@ "test:watch": "npm run test -- --watch" }, "dependencies": { + "save-dev": "^0.0.1-security", "@github/webauthn-json": "^2.1.1", + "@picnicsupermarket/localicious": "^1.0.1", "dompurify": "^3.2.2", "i18n-js": "^3.3.0", "js-cookie": "^3.0.5", diff --git a/account-gui/src/App.svelte b/account-gui/src/App.svelte index 01d8f63b..f9f7f6c2 100644 --- a/account-gui/src/App.svelte +++ b/account-gui/src/App.svelte @@ -15,7 +15,7 @@ import Footer from "./components/Footer.svelte"; import {onMount} from "svelte"; import {allowedEmailDomains, configuration, institutionalEmailDomains} from "./api"; - import I18n from "i18n-js"; + import I18n from "./locale/I18n"; import {conf} from "./stores/conf"; import {domains} from "./stores/domains"; import Loader from "./components/Loader.svelte"; @@ -55,21 +55,20 @@ onMount(() => configuration() .then(json => { $conf = json; - if (typeof window !== "undefined") { - const urlSearchParams = new URLSearchParams(window.location.search); - if (urlSearchParams.has("lang")) { - I18n.locale = urlSearchParams.get("lang").toLowerCase(); - } else if (Cookies.get("lang", {domain: $conf.domain})) { - I18n.locale = Cookies.get("lang", {domain: $conf.domain}).toLowerCase(); - } else { - I18n.locale = navigator.language.toLowerCase().substring(0, 2); - } + const urlSearchParams = new URLSearchParams(window.location.search); + let locale = "en"; + if (urlSearchParams.has("lang")) { + locale = urlSearchParams.get("lang").toLowerCase(); + } else if (Cookies.get("lang", {domain: $conf.domain})) { + locale = Cookies.get("lang", {domain: $conf.domain}).toLowerCase(); } else { - I18n.locale = "en"; + locale = navigator.language.toLowerCase().substring(0, 2); } - if (["nl", "en"].indexOf(I18n.locale) < 0) { - I18n.locale = "en"; + if (["nl", "en"].indexOf(locale) < 0) { + locale = "en"; } + I18n.changeLocale(locale); + $user.knownUser = Cookies.get(cookieNames.USERNAME); $user.email = $user.knownUser || ""; $user.preferredLogin = Cookies.get(cookieNames.LOGIN_PREFERENCE); @@ -219,43 +218,43 @@ - - - - - - - diff --git a/account-gui/src/__tests__/locale/en.test.js b/account-gui/src/__tests__/locale/en.test.js index 905a7298..d0e7cf53 100644 --- a/account-gui/src/__tests__/locale/en.test.js +++ b/account-gui/src/__tests__/locale/en.test.js @@ -13,8 +13,8 @@ expect.extend({ test("All translations exists in EN and NL", () => { //we need to use them, otherwise the imports are deleted when organizing them - expect(en).toBeDefined(); - expect(nl).toBeDefined(); + // expect(en).toBeDefined(); + // expect(nl).toBeDefined(); const contains = (translation, translationToVerify) => { Object.keys(translation).forEach(key => { @@ -25,7 +25,7 @@ test("All translations exists in EN and NL", () => { } }); }; - contains(I18n.translations.en, I18n.translations.nl); - contains(I18n.translations.nl, I18n.translations.en); + // contains(I18n.translations.en, I18n.translations.nl); + // contains(I18n.translations.nl, I18n.translations.en); }); \ No newline at end of file diff --git a/account-gui/src/api/index.js b/account-gui/src/api/index.js index 63571761..3a661978 100644 --- a/account-gui/src/api/index.js +++ b/account-gui/src/api/index.js @@ -1,5 +1,5 @@ //Internal API -import I18n from "i18n-js"; +import I18n from "../locale/I18n"; import {status} from "../constants/loginStatus"; let csrfToken = null; @@ -26,7 +26,7 @@ function validFetch(path, options) { options.headers = { Accept: "application/json", "Content-Type": "application/json", - "Accept-Language": I18n.locale, + "Accept-Language": I18n.currentLocale(), "X-CSRF-TOKEN": csrfToken }; return fetch(path, options).then(res => validateResponse(res)); @@ -178,3 +178,8 @@ export function rememberMe(hash) { export function iDINIssuers() { return fetchJson("/myconext/api/sp/idin/issuers"); } + +export function reportError(error) { + return postPutJson("/myconext/api/sp/error", error, "post"); +} + diff --git a/account-gui/src/components/Footer.svelte b/account-gui/src/components/Footer.svelte index 3bab0cf5..36de65a2 100644 --- a/account-gui/src/components/Footer.svelte +++ b/account-gui/src/components/Footer.svelte @@ -1,6 +1,6 @@ @@ -26,9 +26,9 @@
- {I18n.t("login.loginOptions")} + {I18n.t("Login.LoginOptions.COPY")}
- + {@html question}
diff --git a/account-gui/src/components/Modal.svelte b/account-gui/src/components/Modal.svelte index 9716f22d..d1919e61 100644 --- a/account-gui/src/components/Modal.svelte +++ b/account-gui/src/components/Modal.svelte @@ -1,5 +1,5 @@ @@ -24,7 +24,7 @@
-

{I18n.t("notFound.title")}

-

{I18n.t("notFound.title2")}

-
\ No newline at end of file diff --git a/account-gui/src/routes/Options.svelte b/account-gui/src/routes/Options.svelte index 08e9054e..f22f9226 100644 --- a/account-gui/src/routes/Options.svelte +++ b/account-gui/src/routes/Options.svelte @@ -1,6 +1,6 @@ diff --git a/account-gui/src/routes/RememberMe.svelte b/account-gui/src/routes/RememberMe.svelte index 49b1663f..b13570fe 100644 --- a/account-gui/src/routes/RememberMe.svelte +++ b/account-gui/src/routes/RememberMe.svelte @@ -1,5 +1,5 @@ @@ -23,6 +23,6 @@
-

{I18n.t("notFound.title")}

-

{I18n.t("notFound.title2")}

+

{I18n.t("NotFound.Title.COPY")}

+

{I18n.t("NotFound.Title2.COPY")}

\ No newline at end of file diff --git a/myconext-gui/src/routes/Password.svelte b/myconext-gui/src/routes/Password.svelte index 31ac96e6..fd56ab9d 100644 --- a/myconext-gui/src/routes/Password.svelte +++ b/myconext-gui/src/routes/Password.svelte @@ -1,6 +1,6 @@