Remove personal access token (#1481)

* Refactor NFC scanner tests to use a global variable for platform OS, allowing dynamic switching between iOS and Android during tests. This change improves test isolation and avoids hoisting issues with jest.mock.

* feat: add GitHub App token generation action for self repositories

- Introduced a new action to generate GitHub App tokens for accessing repositories within the selfxyz organization.
- Updated multiple workflows to utilize the new action for token generation, ensuring secure access to private repositories during CI processes.
- Modified Podfile and scripts to support authentication using the generated token, enhancing the cloning of private modules in CI environments.

* chore: enhance CI workflows with Git authentication for CocoaPods

- Updated multiple CI workflows to include a step for configuring Git authentication for CocoaPods, ensuring secure access to private repositories without embedding credentials in URLs.
- Added masking for sensitive tokens in logs to enhance security during CI processes.
- Modified the Podfile to avoid printing authentication details in CI logs, improving overall security practices.

* chore: enhance CI workflows with optional Git authentication configuration

- Added new inputs to the GitHub action for generating GitHub tokens, allowing optional configuration of a ~/.netrc entry for Git authentication.
- Updated multiple CI workflows to utilize the new configuration, improving security and simplifying access to private repositories during builds.
- Removed redundant Git authentication steps from workflows, streamlining the CI process while maintaining secure access to necessary resources.

* chore: update Podfile for secure Git authentication in CI

- Modified the Podfile to enhance security by avoiding the embedding of credentials in URLs for accessing the NFCPassportReader repository during CI processes.
- Added comments to guide developers on using workflow-provided authentication methods, improving overall security practices in the project.
This commit is contained in:
Javier Cortejoso
2025-12-12 12:38:23 +01:00
committed by GitHub
parent 0c54572616
commit 4b09e5b96f
12 changed files with 240 additions and 70 deletions

View File

@@ -0,0 +1,56 @@
name: "Generate GitHub App Token"
description: "Generates a GitHub App token for accessing repositories in the selfxyz organization"
inputs:
app-id:
description: "The GitHub App ID"
required: true
private-key:
description: "The GitHub App private key"
required: true
configure-netrc:
description: "If true, writes a ~/.netrc entry for github.com using the generated token (useful for CocoaPods / git HTTPS fetches)"
required: false
default: "false"
netrc-machine:
description: "The machine hostname to write into ~/.netrc (default: github.com)"
required: false
default: "github.com"
owner:
description: "The owner (organization) of the repositories"
required: false
default: "selfxyz"
repositories:
description: "Comma-separated list of repository names to grant access to"
required: false
default: "NFCPassportReader,android-passport-nfc-reader,react-native-passport-reader,mobile-sdk-native"
outputs:
token:
description: "The generated GitHub App installation token"
value: ${{ steps.app-token.outputs.token }}
runs:
using: "composite"
steps:
- name: Generate GitHub App Token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
id: app-token
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}
owner: ${{ inputs.owner }}
repositories: ${{ inputs.repositories }}
- name: Configure Git auth via ~/.netrc (optional)
if: ${{ inputs.configure-netrc == 'true' }}
shell: bash
run: |
set -euo pipefail
TOKEN="${{ steps.app-token.outputs.token }}"
MACHINE="${{ inputs.netrc-machine }}"
# Mask the token in logs defensively (it shouldn't print, but this protects against future edits).
echo "::add-mask::${TOKEN}"
printf "machine %s\n login x-access-token\n password %s\n" "${MACHINE}" "${TOKEN}" > "${HOME}/.netrc"
chmod 600 "${HOME}/.netrc"

View File

@@ -58,6 +58,14 @@ jobs:
path: |
~/.gradle/caches
~/.gradle/wrapper
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup
with:
@@ -66,7 +74,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Build dependencies
shell: bash
run: yarn workspace @selfxyz/common build
@@ -114,6 +122,14 @@ jobs:
with:
path: app/ios/Pods
lockfile: app/ios/Podfile.lock
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install Mobile Dependencies
uses: ./.github/actions/mobile-setup
with:
@@ -122,7 +138,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Build dependencies
shell: bash
run: yarn workspace @selfxyz/common build

View File

@@ -267,6 +267,7 @@ jobs:
- name: Cache Ruby gems
uses: ./.github/actions/cache-bundler
with:
# TODO(jcortejoso): Confirm the path of the bundle cache
path: app/ios/vendor/bundle
lock-file: app/Gemfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
@@ -314,6 +315,14 @@ jobs:
bundle config set --local path 'vendor/bundle'
bundle install --jobs 4 --retry 3
working-directory: ./app
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install iOS Dependencies
uses: nick-fields/retry@v3
with:
@@ -324,7 +333,7 @@ jobs:
cd app/ios
bundle exec bash scripts/pod-install-with-cache-fix.sh
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Resolve iOS workspace
run: |
WORKSPACE_OPEN="ios/OpenPassport.xcworkspace"
@@ -469,12 +478,19 @@ jobs:
run: |
echo "Cache miss for built dependencies. Building now..."
yarn workspace @selfxyz/mobile-app run build:deps
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Setup Android private modules
run: |
cd ${{ env.APP_PATH }}
PLATFORM=android node scripts/setup-private-modules.cjs
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Android (with AAPT2 symlink fix)
run: yarn android:ci

View File

@@ -386,6 +386,7 @@ jobs:
id: gems-cache
uses: ./.github/actions/cache-bundler
with:
# TODO(jcortejoso): Confirm the path of the bundle cache
path: ${{ env.APP_PATH }}/ios/vendor/bundle
lock-file: app/Gemfile.lock
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
@@ -429,6 +430,14 @@ jobs:
fi
echo "✅ Lock files exist"
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install Mobile Dependencies (main repo)
if: inputs.platform != 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
@@ -439,7 +448,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install Mobile Dependencies (forked PRs - no secrets)
if: inputs.platform != 'android' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
@@ -692,7 +701,7 @@ jobs:
IOS_TESTFLIGHT_GROUPS: ${{ secrets.IOS_TESTFLIGHT_GROUPS }}
NODE_OPTIONS: "--max-old-space-size=8192"
SEGMENT_KEY: ${{ secrets.SEGMENT_KEY }}
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
TURNKEY_AUTH_PROXY_CONFIG_ID: ${{ secrets.TURNKEY_AUTH_PROXY_CONFIG_ID }}
TURNKEY_GOOGLE_CLIENT_ID: ${{ secrets.TURNKEY_GOOGLE_CLIENT_ID }}
@@ -1047,6 +1056,14 @@ jobs:
echo "✅ Lock files exist"
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install Mobile Dependencies (main repo)
if: inputs.platform != 'ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
uses: ./.github/actions/mobile-setup
@@ -1056,7 +1073,7 @@ jobs:
ruby_version: ${{ env.RUBY_VERSION }}
workspace: ${{ env.WORKSPACE }}
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
PLATFORM: ${{ inputs.platform }}
- name: Install Mobile Dependencies (forked PRs - no secrets)
@@ -1113,7 +1130,7 @@ jobs:
cd ${{ env.APP_PATH }}
PLATFORM=android node scripts/setup-private-modules.cjs
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Dependencies (Android)

View File

@@ -70,6 +70,14 @@ jobs:
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -79,7 +87,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -138,7 +146,7 @@ jobs:
cd app
PLATFORM=android node scripts/setup-private-modules.cjs
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
CI: true
- name: Build Android APK
run: |
@@ -149,6 +157,8 @@ jobs:
- name: Clean up Gradle build artifacts
uses: ./.github/actions/cleanup-gradle-artifacts
- name: Verify APK and android-passport-nfc-reader integration
env:
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
run: |
echo "🔍 Verifying build artifacts..."
APK_PATH="app/android/app/build/outputs/apk/debug/app-debug.apk"
@@ -160,8 +170,8 @@ jobs:
echo "📱 APK size: $APK_SIZE bytes"
# Verify private modules were properly integrated (skip for forks)
if [ -z "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]; then
echo "🔕 No PAT available — skipping private module verification"
if [ -z "${SELFXYZ_APP_TOKEN:-}" ]; then
echo "🔕 No SELFXYZ_APP_TOKEN available — skipping private module verification"
else
# Verify android-passport-nfc-reader
if [ -d "app/android/android-passport-nfc-reader" ]; then
@@ -263,6 +273,14 @@ jobs:
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
configure-netrc: "true"
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -272,7 +290,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -360,7 +378,7 @@ jobs:
echo "📦 Installing pods via centralized script…"
BUNDLE_GEMFILE=../Gemfile bundle exec bash scripts/pod-install-with-cache-fix.sh
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Setup iOS Simulator
run: |
echo "Setting up iOS Simulator..."

View File

@@ -73,6 +73,13 @@ jobs:
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -82,7 +89,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -237,6 +244,13 @@ jobs:
- name: Toggle Yarn hardened mode for trusted PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
run: echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV
- name: Generate token for self repositories
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: ./.github/actions/generate-github-token
id: github-token
with:
app-id: ${{ vars.GH_WORKFLOWS_CROSS_ACCESS_ID }}
private-key: ${{ secrets.GH_WORKFLOWS_CROSS_ACCESS_KEY }}
- name: Install deps (internal PRs and protected branches)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
uses: nick-fields/retry@v3
@@ -246,7 +260,7 @@ jobs:
retry_wait_seconds: 5
command: yarn install --immutable --silent
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
- name: Install deps (forked PRs - no secrets)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
uses: nick-fields/retry@v3
@@ -322,15 +336,15 @@ jobs:
max_attempts: 3
retry_wait_seconds: 10
command: |
if [ -n "${SELFXYZ_INTERNAL_REPO_PAT}" ]; then
echo "🔑 Using SELFXYZ_INTERNAL_REPO_PAT for private pod access"
echo "::add-mask::${SELFXYZ_INTERNAL_REPO_PAT}"
if [ -n "${SELFXYZ_APP_TOKEN}" ]; then
echo "🔑 Using GitHub App token for private pod access"
echo "::add-mask::${SELFXYZ_APP_TOKEN}"
fi
cd packages/mobile-sdk-demo/ios
echo "📦 Installing pods via cache-fix script…"
bash scripts/pod-install-with-cache-fix.sh
env:
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
SELFXYZ_APP_TOKEN: ${{ steps.github-token.outputs.token }}
GIT_TERMINAL_PROMPT: 0
- name: Setup iOS Simulator
run: |

View File

@@ -33,7 +33,7 @@ def using_https_git_auth?
auth_data.include?("Logged in to github.com account") &&
auth_data.include?("Git operations protocol: https")
rescue => e
puts "gh auth status failed, assuming no HTTPS auth -- will try SSH"
# Avoid printing auth-related details in CI logs.
false
end
end
@@ -51,18 +51,16 @@ target "Self" do
# External fork - use public NFCPassportReader repository (placeholder)
# TODO: Replace with actual public NFCPassportReader repository URL
nfc_repo_url = "https://github.com/PLACEHOLDER/NFCPassportReader.git"
puts "📦 Using public NFCPassportReader for external fork (#{ENV["GITHUB_REPOSITORY"]})"
elsif ENV["GITHUB_ACTIONS"] == "true" && ENV["SELFXYZ_INTERNAL_REPO_PAT"]
# Running in selfxyz GitHub Actions with PAT available - use private repo with token
nfc_repo_url = "https://#{ENV["SELFXYZ_INTERNAL_REPO_PAT"]}@github.com/selfxyz/NFCPassportReader.git"
puts "📦 Using private NFCPassportReader with PAT (selfxyz GitHub Actions)"
elsif ENV["GITHUB_ACTIONS"] == "true"
# CI: NEVER embed credentials in URLs. Rely on workflow-provided auth via:
# - ~/.netrc or a Git credential helper, and token masking in logs.
nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
elsif using_https_git_auth?
# Local development with HTTPS GitHub auth via gh - use HTTPS to private repo
nfc_repo_url = "https://github.com/selfxyz/NFCPassportReader.git"
else
# Local development in selfxyz repo - use SSH to private repo
nfc_repo_url = "git@github.com:selfxyz/NFCPassportReader.git"
puts "📦 Using SSH for private NFCPassportReader (local selfxyz development)"
end
pod "NFCPassportReader", git: nfc_repo_url, commit: "9eff7c4e3a9037fdc1e03301584e0d5dcf14d76b"

View File

@@ -109,7 +109,13 @@ clone_private_module() {
local dir_name=$(basename "$target_dir")
# Use different clone methods based on environment
if is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then
if is_ci && [[ -n "${SELFXYZ_APP_TOKEN:-}" ]]; then
# CI environment with GitHub App installation token
git clone "https://x-access-token:${SELFXYZ_APP_TOKEN}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone $repo_name with GitHub App token"
exit 1
}
elif is_ci && [[ -n "${SELFXYZ_INTERNAL_REPO_PAT:-}" ]]; then
# CI environment with PAT (fallback if action didn't run)
git clone "https://${SELFXYZ_INTERNAL_REPO_PAT}@github.com/selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone $repo_name with PAT"
@@ -119,14 +125,14 @@ clone_private_module() {
# Local development with SSH
git clone "git@github.com:selfxyz/${repo_name}.git" "$dir_name" || {
log "ERROR: Failed to clone $repo_name with SSH"
log "Please ensure you have SSH access to the repository or set SELFXYZ_INTERNAL_REPO_PAT"
log "Please ensure you have SSH access to the repository or set SELFXYZ_APP_TOKEN/SELFXYZ_INTERNAL_REPO_PAT"
exit 1
}
else
log "ERROR: No authentication method available for cloning $repo_name"
log "Please either:"
log " - Set up SSH access (for local development)"
log " - Set SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)"
log " - Set SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT environment variable (for CI)"
exit 1
fi
@@ -194,14 +200,15 @@ log "✅ Package files backed up successfully"
# Install SDK from tarball in app with timeout
log "Installing SDK as real files..."
if is_ci; then
# Temporarily unset PAT to skip private modules during SDK installation
env -u SELFXYZ_INTERNAL_REPO_PAT timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || {
# Temporarily unset both auth tokens to skip private modules during SDK installation
# Both tokens must be unset to prevent setup-private-modules.cjs from attempting clones
env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN timeout 180 yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH" || {
log "SDK installation timed out after 3 minutes"
exit 1
}
else
# Temporarily unset PAT to skip private modules during SDK installation
env -u SELFXYZ_INTERNAL_REPO_PAT yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
# Temporarily unset both auth tokens to skip private modules during SDK installation
env -u SELFXYZ_INTERNAL_REPO_PAT -u SELFXYZ_APP_TOKEN yarn add "@selfxyz/mobile-sdk-alpha@file:$TARBALL_PATH"
fi
# Verify installation (check for AAR file in both local and hoisted locations)

View File

@@ -29,8 +29,9 @@ const PRIVATE_MODULES = [
// Environment detection
// CI is set by GitHub Actions, CircleCI, etc. Check for truthy value
const isCI = !!process.env.CI || process.env.GITHUB_ACTIONS === 'true';
const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
const repoToken = process.env.SELFXYZ_INTERNAL_REPO_PAT;
const appToken = process.env.SELFXYZ_APP_TOKEN; // GitHub App installation token
const isDryRun = process.env.DRY_RUN === 'true';
// Platform detection for Android-specific modules
@@ -150,13 +151,17 @@ function clonePrivateRepo(repoName, localPath) {
let cloneUrl;
if (isCI && repoToken) {
if (isCI && appToken) {
// CI environment with GitHub App installation token
log('CI detected: Using SELFXYZ_APP_TOKEN for clone', 'info');
cloneUrl = `https://x-access-token:${appToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
} else if (isCI && repoToken) {
// CI environment with Personal Access Token
log('CI detected: Using SELFXYZ_INTERNAL_REPO_PAT for clone', 'info');
cloneUrl = `https://${repoToken}@github.com/${GITHUB_ORG}/${repoName}.git`;
} else if (isCI) {
log(
'CI environment detected but SELFXYZ_INTERNAL_REPO_PAT not available - skipping private module setup',
'CI environment detected but no token available - skipping private module setup',
'info',
);
log(
@@ -173,7 +178,7 @@ function clonePrivateRepo(repoName, localPath) {
}
// Security: Use quiet mode for credentialed URLs to prevent token exposure
const isCredentialedUrl = isCI && repoToken;
const isCredentialedUrl = isCI && (appToken || repoToken);
const quietFlag = isCredentialedUrl ? '--quiet' : '';
const targetDir = path.basename(localPath);
const cloneCommand = `git clone --branch ${BRANCH} --single-branch --depth 1 ${quietFlag} "${cloneUrl}" "${targetDir}"`;
@@ -190,7 +195,7 @@ function clonePrivateRepo(repoName, localPath) {
} catch (error) {
if (isCI) {
log(
'Clone failed in CI environment. Check SELFXYZ_INTERNAL_REPO_PAT permissions.',
'Clone failed in CI environment. Check SELFXYZ_APP_TOKEN or SELFXYZ_INTERNAL_REPO_PAT permissions.',
'error',
);
} else {
@@ -231,7 +236,7 @@ function setupPrivateModule(module) {
}
// Security: Remove credential-embedded remote URL after clone
if (isCI && repoToken && !isDryRun) {
if (isCI && (appToken || repoToken) && !isDryRun) {
scrubGitRemoteUrl(localPath, repoName);
}
@@ -275,6 +280,11 @@ function setupAndroidPassportReader() {
`Setup complete: ${successCount}/${PRIVATE_MODULES.length} modules cloned`,
'warning',
);
} else {
log(
'No private modules were cloned - this is expected for forked PRs',
'info',
);
}
}

View File

@@ -3,19 +3,35 @@
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
// Mock Platform without requiring react-native to avoid memory issues
// Use a simple object that can be modified directly
// Use a global variable with getter to allow per-test platform switching
// This pattern avoids hoisting issues with jest.mock
import { Buffer } from 'buffer';
import { parseScanResponse, scan } from '@/integrations/nfc/nfcScanner';
import { PassportReader } from '@/integrations/nfc/passportReader';
const Platform = {
OS: 'ios', // Default to iOS
Version: 14,
};
// Declare global variable for platform OS that can be modified per-test
declare global {
// eslint-disable-next-line no-var
var mockPlatformOS: 'ios' | 'android';
}
// Initialize the global mock platform - default to iOS
global.mockPlatformOS = 'ios';
// Override the react-native mock from jest.setup.js with a getter-based Platform
// This allows tests to change Platform.OS dynamically by modifying global.mockPlatformOS
jest.mock('react-native', () => ({
Platform,
Platform: {
get OS() {
return global.mockPlatformOS;
},
Version: 14,
select: jest.fn((obj: Record<string, unknown>) => {
const os = global.mockPlatformOS;
return obj[os] || obj.default;
}),
},
}));
// Ensure the Node Buffer implementation is available to the module under test
@@ -25,7 +41,7 @@ describe('parseScanResponse', () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset Platform.OS to default before each test to prevent pollution
Platform.OS = 'ios';
global.mockPlatformOS = 'ios';
});
it('parses iOS response', () => {
@@ -93,9 +109,8 @@ describe('parseScanResponse', () => {
});
it('parses Android response', () => {
// Temporarily override Platform.OS for this test
const originalOS = Platform.OS;
Platform.OS = 'android';
// Set Platform.OS to android for this test
global.mockPlatformOS = 'android';
const mrz =
'P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<L898902C<3UTO6908061F9406236ZE184226B<<<<<14';
@@ -159,9 +174,6 @@ describe('parseScanResponse', () => {
// dg2Hash should be parsed from hex string '1234': 12 = 18, 34 = 52
expect(result.dg2Hash).toEqual([18, 52]);
expect(result.dgPresents).toEqual([1, 2]);
// Restore original value
Platform.OS = originalOS;
});
it('handles malformed iOS response', () => {
@@ -172,8 +184,8 @@ describe('parseScanResponse', () => {
});
it('handles malformed Android response', () => {
const originalOS = Platform.OS;
Platform.OS = 'android';
// Set Platform.OS to android for this test
global.mockPlatformOS = 'android';
const response = {
mrz: 'valid_mrz',
@@ -182,9 +194,6 @@ describe('parseScanResponse', () => {
};
expect(() => parseScanResponse(response)).toThrow();
// Restore original value
Platform.OS = originalOS;
});
it('handles missing required fields', () => {
@@ -232,7 +241,7 @@ describe('scan', () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset Platform.OS to default before each test to prevent pollution
Platform.OS = 'ios';
global.mockPlatformOS = 'ios';
// Reset PassportReader mock before each test
// The implementation checks for scanPassport property, so we need to ensure it exists
Object.defineProperty(PassportReader, 'scanPassport', {

View File

@@ -18,6 +18,7 @@ const BRANCH = 'main';
// Environment detection
const isCI = process.env.CI === 'true';
const repoToken = process.env.SELFXYZ_INTERNAL_REPO_PAT;
const appToken = process.env.SELFXYZ_APP_TOKEN; // GitHub App installation token
const isDryRun = process.env.DRY_RUN === 'true';
function log(message, type = 'info') {
@@ -89,19 +90,24 @@ function setupSubmodule() {
let submoduleUrl;
if (isCI && repoToken) {
if (isCI && appToken) {
// CI environment with GitHub App installation token
// Security: NEVER embed credentials in git URLs. Rely on CI-provided auth via:
// - ~/.netrc, a Git credential helper, or SSH agent configuration.
submoduleUrl = `https://github.com/${GITHUB_ORG}/${REPO_NAME}.git`;
} else if (isCI && repoToken) {
// CI environment with Personal Access Token
log('CI detected: Using SELFXYZ_INTERNAL_REPO_PAT for submodule', 'info');
submoduleUrl = `https://${repoToken}@github.com/${GITHUB_ORG}/${REPO_NAME}.git`;
// Security: NEVER embed credentials in git URLs. Rely on CI-provided auth via:
// - ~/.netrc, a Git credential helper, or SSH agent configuration.
submoduleUrl = `https://github.com/${GITHUB_ORG}/${REPO_NAME}.git`;
} else if (isCI) {
log('CI environment detected but SELFXYZ_INTERNAL_REPO_PAT not available - skipping private module setup', 'info');
log('CI environment detected but no token available - skipping private module setup', 'info');
log('This is expected for forked PRs or environments without access to private modules', 'info');
return false; // Return false to indicate setup was skipped
} else if (usingHTTPSGitAuth()) {
submoduleUrl = `https://github.com/${GITHUB_ORG}/${REPO_NAME}.git`;
} else {
// Local development with SSH
log('Local development: Using SSH for submodule', 'info');
submoduleUrl = `git@github.com:${GITHUB_ORG}/${REPO_NAME}.git`;
}
@@ -113,7 +119,7 @@ function setupSubmodule() {
} else {
// Add submodule
const addCommand = `git submodule add -b ${BRANCH} "${submoduleUrl}" mobile-sdk-native`;
if (isCI && repoToken) {
if (isCI && (appToken || repoToken)) {
// Security: Run command silently to avoid token exposure in logs
runCommand(addCommand, { stdio: 'pipe' });
} else {
@@ -125,7 +131,7 @@ function setupSubmodule() {
return true; // Return true to indicate successful setup
} catch (error) {
if (isCI) {
log('Submodule setup failed in CI environment. Check SELFXYZ_INTERNAL_REPO_PAT permissions.', 'error');
log('Submodule setup failed in CI environment. Check repository access/credentials configuration.', 'error');
} else {
log('Submodule setup failed. Ensure you have SSH access to the repository.', 'error');
}
@@ -169,7 +175,7 @@ function setupMobileSDKNative() {
}
// Security: Remove credential-embedded remote URL after setup
if (isCI && repoToken && !isDryRun) {
if (isCI && (appToken || repoToken) && !isDryRun) {
scrubGitRemoteUrl();
}

View File

@@ -46,12 +46,15 @@ target "SelfDemoApp" do
nfc_repo_url = if !is_selfxyz_repo
puts "📦 Using public NFCPassportReader for external fork (#{ENV["GITHUB_REPOSITORY"]})"
"https://github.com/PLACEHOLDER/NFCPassportReader.git"
elsif ENV["GITHUB_ACTIONS"] == "true" && ENV["SELFXYZ_INTERNAL_REPO_PAT"] && !ENV["SELFXYZ_INTERNAL_REPO_PAT"].empty?
puts "📦 Using private NFCPassportReader with PAT (selfxyz GitHub Actions)"
"https://#{ENV["SELFXYZ_INTERNAL_REPO_PAT"]}@github.com/selfxyz/NFCPassportReader.git"
elsif ENV["GITHUB_ACTIONS"] == "true"
# CI: NEVER embed credentials in URLs. Rely on workflow-provided auth via:
# - ~/.netrc or a Git credential helper, and token masking in logs.
"https://github.com/selfxyz/NFCPassportReader.git"
elsif using_https_git_auth?
# Local development with HTTPS GitHub auth via gh - use HTTPS to private repo
"https://github.com/selfxyz/NFCPassportReader.git"
else
# Local development in selfxyz repo - use SSH to private repo
puts "📦 Using SSH for private NFCPassportReader (local selfxyz development)"
"git@github.com:selfxyz/NFCPassportReader.git"
end