Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fa7144a7b | ||
|
|
b5286ca6db | ||
|
|
ad32e731fb | ||
|
|
70ac1cd90e | ||
|
|
62d7c929fe |
@@ -0,0 +1,77 @@
|
||||
name: Release APK
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '17'
|
||||
|
||||
- name: Set up Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Build release APK
|
||||
run: |
|
||||
chmod +x ./gradlew
|
||||
./gradlew :app:assembleRelease --no-daemon
|
||||
mkdir -p dist
|
||||
cp app/build/outputs/apk/release/app-release.apk "dist/homelab-mfa.apk"
|
||||
|
||||
- name: Generate release notes
|
||||
id: notes
|
||||
run: |
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
PREV=$(git tag --sort=-creatordate | grep -A1 -x "$TAG" | tail -n1)
|
||||
if [ -n "$PREV" ] && [ "$PREV" != "$TAG" ]; then
|
||||
RANGE="$PREV..$TAG"
|
||||
else
|
||||
RANGE=""
|
||||
fi
|
||||
echo "## $TAG" > notes.md
|
||||
echo "" >> notes.md
|
||||
git log $RANGE --no-merges --pretty=format:'- %s' | grep -viE '^- (merge|wip|fixup)' >> notes.md || true
|
||||
echo "" >> notes.md
|
||||
echo "" >> notes.md
|
||||
echo "**Install:** download \`homelab-mfa.apk\` below." >> notes.md
|
||||
|
||||
- name: Publish release + upload APK
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||
BODY=$(python3 -c "import json,sys; print(json.dumps(open('notes.md').read()))")
|
||||
# find existing release for this tag
|
||||
REL=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG")
|
||||
ID=$(echo "$REL" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || echo "")
|
||||
if [ -z "$ID" ]; then
|
||||
ID=$(curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
"$API/releases" -d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":$BODY}" \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
|
||||
else
|
||||
curl -s -X PATCH -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
"$API/releases/$ID" -d "{\"name\":\"$TAG\",\"body\":$BODY}" >/dev/null
|
||||
fi
|
||||
# remove existing same-named asset, then upload fresh
|
||||
for AID in $(curl -s -H "Authorization: token $TOKEN" "$API/releases/$ID/assets" \
|
||||
| python3 -c "import json,sys;[print(a['id']) for a in json.load(sys.stdin) if a['name']=='homelab-mfa.apk']" 2>/dev/null); do
|
||||
curl -s -X DELETE -H "Authorization: token $TOKEN" "$API/releases/$ID/assets/$AID" >/dev/null
|
||||
done
|
||||
curl -s -X POST -H "Authorization: token $TOKEN" \
|
||||
"$API/releases/$ID/assets?name=homelab-mfa.apk" \
|
||||
-F attachment=@dist/homelab-mfa.apk >/dev/null
|
||||
echo "Released $TAG (id $ID) with homelab-mfa.apk"
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
*.iml
|
||||
.gradle/
|
||||
.kotlin/
|
||||
local.properties
|
||||
.idea/
|
||||
.DS_Store
|
||||
/build/
|
||||
app/build/
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.keystore
|
||||
*.jks
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
#Thu Jun 04 12:16:33 UTC 2026
|
||||
gradle.version=8.6
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,73 @@
|
||||
# Homelab MFA
|
||||
|
||||
Biometric push-approval for your homelab logins. When you sign in through Authentik, your phone
|
||||
buzzes — approve with a fingerprint, or deny with one tap. A self-hosted alternative to Duo /
|
||||
Authy push, with **no third-party cloud** in the loop.
|
||||
|
||||
## How it works
|
||||
|
||||
1. You log in via **Authentik**. A flow step fires an **ntfy** notification to your phone with two
|
||||
action buttons: **Approve** and **Deny**.
|
||||
2. The buttons are `homemfa://approve?token=…` / `homemfa://deny?token=…` deep links that open this app.
|
||||
3. **Approve** opens an in-app review screen showing *who is asking* (see context params below), then
|
||||
triggers a biometric prompt (fingerprint / face / device PIN). On success the app POSTs
|
||||
`approve:<token>` to `ntfy.khodak.me/mfa-approve`; your Authentik flow is listening on that topic and
|
||||
completes the login. You can also **Deny** right from that screen.
|
||||
4. **Deny** POSTs `deny:<token>` to `mfa-deny` and blocks the login — no biometric needed to reject.
|
||||
|
||||
### Optional context params
|
||||
|
||||
The approve link may carry extra query params so you can see what you're approving before you do:
|
||||
|
||||
| Param | Shown as | Example |
|
||||
|--------|---------------------|------------------------|
|
||||
| `app` | 🖥 application name | `app=Grafana` |
|
||||
| `user` | 👤 username | `user=amir` |
|
||||
| `ip` | 📍 source IP | `ip=203.0.113.44` |
|
||||
| `geo` | appended to the IP | `geo=Berlin, DE` |
|
||||
| `ts` | request timestamp (epoch ms) — used to **expire** stale prompts after 2 min | `ts=1750000000000` |
|
||||
|
||||
All are optional and URL-encoded; with none present the app just shows a plain Approve / Deny prompt.
|
||||
|
||||
The activity is `showWhenLocked` + `turnScreenOn`, so an approval works straight from the lock screen.
|
||||
|
||||
## Features
|
||||
|
||||
- **Biometric gate** — `BIOMETRIC_STRONG` or device credential required to approve (deny is instant)
|
||||
- **Login context** — see the app, user, source IP and geo of the request before approving
|
||||
- **One-tap Deny** — reject a suspicious login straight from the approval screen, no biometric needed
|
||||
- **Stale-request expiry** — prompts older than 2 minutes can't be approved (replay safety net)
|
||||
- **Lock-screen ready** — wakes the screen and shows over the keyguard
|
||||
- **Self-hosted** — talks only to your own ntfy instance; no Firebase, no vendor cloud
|
||||
- **Zero stored secrets** — stateless; each request carries a one-time token
|
||||
- **Setup self-check** — a "Test Biometric" button confirms enrollment before you rely on it
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/setup.png" width="240" alt="Setup / ready screen">
|
||||
|
||||
<img src="docs/screenshots/approve-context.png" width="240" alt="Approval prompt with login context">
|
||||
|
||||
<img src="docs/screenshots/approved.png" width="240" alt="Login approved">
|
||||
</p>
|
||||
|
||||
## Install
|
||||
|
||||
1. Download `homelab-mfa.apk` from the [latest release](../../releases/latest)
|
||||
2. On your Android phone: **Settings → Apps → Install unknown apps** → allow your browser/file manager
|
||||
3. Open the APK and tap Install
|
||||
4. Open **Homelab MFA** and tap **Test Biometric** to confirm your fingerprint is enrolled
|
||||
|
||||
## Server side
|
||||
|
||||
You need:
|
||||
|
||||
- An **ntfy** instance reachable at `ntfy.khodak.me` (or change the host in `MainActivity.kt`)
|
||||
- An **Authentik** flow that, on login, publishes a notification with the two `homemfa://` action
|
||||
buttons, then waits on the `mfa-approve` / `mfa-deny` topics for the decision
|
||||
|
||||
## Requirements
|
||||
|
||||
- Android 9.0+ (API 28)
|
||||
- A fingerprint, face, or device PIN enrolled
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId = "me.khodak.mfa"
|
||||
minSdk = 28
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
versionCode = 3
|
||||
versionName = "1.2"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
// Generated by view binder compiler. Do not edit!
|
||||
package me.khodak.mfa.databinding;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
import androidx.viewbinding.ViewBindings;
|
||||
import java.lang.NullPointerException;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import me.khodak.mfa.R;
|
||||
|
||||
public final class ActivityMainBinding implements ViewBinding {
|
||||
@NonNull
|
||||
private final LinearLayout rootView;
|
||||
|
||||
@NonNull
|
||||
public final ImageView icon;
|
||||
|
||||
@NonNull
|
||||
public final TextView statusText;
|
||||
|
||||
@NonNull
|
||||
public final TextView subText;
|
||||
|
||||
private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull ImageView icon,
|
||||
@NonNull TextView statusText, @NonNull TextView subText) {
|
||||
this.rootView = rootView;
|
||||
this.icon = icon;
|
||||
this.statusText = statusText;
|
||||
this.subText = subText;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public LinearLayout getRoot() {
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
|
||||
return inflate(inflater, null, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup parent, boolean attachToParent) {
|
||||
View root = inflater.inflate(R.layout.activity_main, parent, false);
|
||||
if (attachToParent) {
|
||||
parent.addView(root);
|
||||
}
|
||||
return bind(root);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ActivityMainBinding bind(@NonNull View rootView) {
|
||||
// The body of this method is generated in a way you would not otherwise write.
|
||||
// This is done to optimize the compiled bytecode for size and performance.
|
||||
int id;
|
||||
missingId: {
|
||||
id = R.id.icon;
|
||||
ImageView icon = ViewBindings.findChildViewById(rootView, id);
|
||||
if (icon == null) {
|
||||
break missingId;
|
||||
}
|
||||
|
||||
id = R.id.statusText;
|
||||
TextView statusText = ViewBindings.findChildViewById(rootView, id);
|
||||
if (statusText == null) {
|
||||
break missingId;
|
||||
}
|
||||
|
||||
id = R.id.subText;
|
||||
TextView subText = ViewBindings.findChildViewById(rootView, id);
|
||||
if (subText == null) {
|
||||
break missingId;
|
||||
}
|
||||
|
||||
return new ActivityMainBinding((LinearLayout) rootView, icon, statusText, subText);
|
||||
}
|
||||
String missingId = rootView.getResources().getResourceName(id);
|
||||
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
// Generated by view binder compiler. Do not edit!
|
||||
package me.khodak.mfa.databinding;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
import androidx.viewbinding.ViewBindings;
|
||||
import java.lang.NullPointerException;
|
||||
import java.lang.Override;
|
||||
import java.lang.String;
|
||||
import me.khodak.mfa.R;
|
||||
|
||||
public final class ActivityMainBinding implements ViewBinding {
|
||||
@NonNull
|
||||
private final LinearLayout rootView;
|
||||
|
||||
@NonNull
|
||||
public final ImageView icon;
|
||||
|
||||
@NonNull
|
||||
public final TextView statusText;
|
||||
|
||||
@NonNull
|
||||
public final TextView subText;
|
||||
|
||||
private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull ImageView icon,
|
||||
@NonNull TextView statusText, @NonNull TextView subText) {
|
||||
this.rootView = rootView;
|
||||
this.icon = icon;
|
||||
this.statusText = statusText;
|
||||
this.subText = subText;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public LinearLayout getRoot() {
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
|
||||
return inflate(inflater, null, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup parent, boolean attachToParent) {
|
||||
View root = inflater.inflate(R.layout.activity_main, parent, false);
|
||||
if (attachToParent) {
|
||||
parent.addView(root);
|
||||
}
|
||||
return bind(root);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ActivityMainBinding bind(@NonNull View rootView) {
|
||||
// The body of this method is generated in a way you would not otherwise write.
|
||||
// This is done to optimize the compiled bytecode for size and performance.
|
||||
int id;
|
||||
missingId: {
|
||||
id = R.id.icon;
|
||||
ImageView icon = ViewBindings.findChildViewById(rootView, id);
|
||||
if (icon == null) {
|
||||
break missingId;
|
||||
}
|
||||
|
||||
id = R.id.statusText;
|
||||
TextView statusText = ViewBindings.findChildViewById(rootView, id);
|
||||
if (statusText == null) {
|
||||
break missingId;
|
||||
}
|
||||
|
||||
id = R.id.subText;
|
||||
TextView subText = ViewBindings.findChildViewById(rootView, id);
|
||||
if (subText == null) {
|
||||
break missingId;
|
||||
}
|
||||
|
||||
return new ActivityMainBinding((LinearLayout) rootView, icon, statusText, subText);
|
||||
}
|
||||
String missingId = rootView.getResources().getResourceName(id);
|
||||
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
-keep class androidx.core.app.CoreComponentFactory { <init>(); }
|
||||
-keep class androidx.profileinstaller.ProfileInstallReceiver { <init>(); }
|
||||
-keep class androidx.startup.InitializationProvider { <init>(); }
|
||||
-keep class me.khodak.mfa.MainActivity { <init>(); }
|
||||
-keep class android.widget.Space { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.app.AlertController$RecycleListView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.view.menu.ActionMenuItemView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.view.menu.ExpandedMenuView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.view.menu.ListMenuItemView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionBarContainer { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionBarContextView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionBarOverlayLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionMenuView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActivityChooserView$InnerLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.AlertDialogLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ButtonBarLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ContentFrameLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.DialogTitle { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.FitWindowsFrameLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.FitWindowsLinearLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.SearchView$SearchAutoComplete { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.Toolbar { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ViewStubCompat { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.constraintlayout.helper.widget.Flow { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.constraintlayout.widget.ConstraintLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.coordinatorlayout.widget.CoordinatorLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.core.widget.NestedScrollView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.fragment.app.FragmentContainerView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.recyclerview.widget.RecyclerView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.appbar.MaterialToolbar { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.button.MaterialButton { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.button.MaterialButtonToggleGroup { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.chip.Chip { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.datepicker.MaterialCalendarGridView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.internal.BaselineLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.internal.CheckableImageButton { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.internal.ClippableRoundedCornerLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.internal.NavigationMenuItemView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.internal.NavigationMenuView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.internal.TouchObserverFrameLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.snackbar.Snackbar$SnackbarLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.snackbar.SnackbarContentLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.textfield.TextInputEditText { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.textfield.TextInputLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.timepicker.ChipTextInputComboView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.timepicker.ClockFaceView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.timepicker.ClockHandView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class com.google.android.material.timepicker.TimePickerView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,2 +0,0 @@
|
||||
#- File Locator -
|
||||
listingFile=../../../outputs/apk/debug/output-metadata.json
|
||||
@@ -1,2 +0,0 @@
|
||||
#- File Locator -
|
||||
listingFile=../../../outputs/apk/release/output-metadata.json
|
||||
@@ -1,2 +0,0 @@
|
||||
appMetadataVersion=1.1
|
||||
androidGradlePluginVersion=8.2.2
|
||||
@@ -1,2 +0,0 @@
|
||||
appMetadataVersion=1.1
|
||||
androidGradlePluginVersion=8.2.2
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="me.khodak.mfa"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="28"
|
||||
android:targetSdkVersion="34" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
<!-- suppress DeprecatedClassUsageInspection -->
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<permission
|
||||
android:name="me.khodak.mfa.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-permission android:name="me.khodak.mfa.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:appComponentFactory="androidx.core.app.CoreComponentFactory"
|
||||
android:extractNativeLibs="false"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@drawable/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.HomelabMFA"
|
||||
android:usesCleartextTraffic="false" >
|
||||
<activity
|
||||
android:name="me.khodak.mfa.MainActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:showWhenLocked="true"
|
||||
android:turnScreenOn="true" >
|
||||
|
||||
<!-- Handle homemfa://approve?token=... and homemfa://deny?token=... -->
|
||||
<intent-filter android:autoVerify="false" >
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="homemfa" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="me.khodak.mfa.androidx-startup"
|
||||
android:exported="false" >
|
||||
<meta-data
|
||||
android:name="androidx.emoji2.text.EmojiCompatInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="androidx.profileinstaller.ProfileInstallerInitializer"
|
||||
android:value="androidx.startup" />
|
||||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name="androidx.profileinstaller.ProfileInstallReceiver"
|
||||
android:directBootAware="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.DUMP" >
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.INSTALL_PROFILE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.SKIP_FILE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.SAVE_PROFILE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "COMPATIBLE_SCREEN_MANIFEST",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "me.khodak.mfa.debug",
|
||||
"variantName": "debug",
|
||||
"elements": []
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "COMPATIBLE_SCREEN_MANIFEST",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "me.khodak.mfa",
|
||||
"variantName": "release",
|
||||
"elements": []
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-1
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="activity_main" modulePackage="me.khodak.mfa" filePath="app/src/main/res/layout/activity_main.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.LinearLayout"><Targets><Target tag="layout/activity_main_0" view="LinearLayout"><Expressions/><location startLine="1" startOffset="0" endLine="36" endOffset="14"/></Target><Target id="@+id/icon" view="ImageView"><Expressions/><location startLine="9" startOffset="4" endLine="14" endOffset="50"/></Target><Target id="@+id/statusText" view="TextView"><Expressions/><location startLine="16" startOffset="4" endLine="24" endOffset="49"/></Target><Target id="@+id/subText" view="TextView"><Expressions/><location startLine="26" startOffset="4" endLine="34" endOffset="34"/></Target></Targets></Layout>
|
||||
-1
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="activity_main" modulePackage="me.khodak.mfa" filePath="app/src/main/res/layout/activity_main.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.LinearLayout"><Targets><Target tag="layout/activity_main_0" view="LinearLayout"><Expressions/><location startLine="1" startOffset="0" endLine="36" endOffset="14"/></Target><Target id="@+id/icon" view="ImageView"><Expressions/><location startLine="9" startOffset="4" endLine="14" endOffset="50"/></Target><Target id="@+id/statusText" view="TextView"><Expressions/><location startLine="16" startOffset="4" endLine="24" endOffset="49"/></Target><Target id="@+id/subText" view="TextView"><Expressions/><location startLine="26" startOffset="4" endLine="34" endOffset="34"/></Target></Targets></Layout>
|
||||
-1
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="activity_main" modulePackage="me.khodak.mfa" filePath="app/src/main/res/layout/activity_main.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.LinearLayout"><Targets><Target tag="layout/activity_main_0" view="LinearLayout"><Expressions/><location startLine="1" startOffset="0" endLine="36" endOffset="14"/></Target><Target id="@+id/icon" view="ImageView"><Expressions/><location startLine="9" startOffset="4" endLine="14" endOffset="50"/></Target><Target id="@+id/statusText" view="TextView"><Expressions/><location startLine="16" startOffset="4" endLine="24" endOffset="49"/></Target><Target id="@+id/subText" view="TextView"><Expressions/><location startLine="26" startOffset="4" endLine="34" endOffset="34"/></Target></Targets></Layout>
|
||||
-1
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout layout="activity_main" modulePackage="me.khodak.mfa" filePath="app/src/main/res/layout/activity_main.xml" directory="layout" isMerge="false" isBindingData="false" rootNodeType="android.widget.LinearLayout"><Targets><Target tag="layout/activity_main_0" view="LinearLayout"><Expressions/><location startLine="1" startOffset="0" endLine="36" endOffset="14"/></Target><Target id="@+id/icon" view="ImageView"><Expressions/><location startLine="9" startOffset="4" endLine="14" endOffset="50"/></Target><Target id="@+id/statusText" view="TextView"><Expressions/><location startLine="16" startOffset="4" endLine="24" endOffset="49"/></Target><Target id="@+id/subText" view="TextView"><Expressions/><location startLine="26" startOffset="4" endLine="34" endOffset="34"/></Target></Targets></Layout>
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
# This is a configuration file for ProGuard.
|
||||
# http://proguard.sourceforge.net/index.html#manual/usage.html
|
||||
#
|
||||
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
|
||||
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
|
||||
# will be ignored by new version of the Android plugin for Gradle.
|
||||
|
||||
# Optimizations: If you don't want to optimize, use the proguard-android.txt configuration file
|
||||
# instead of this one, which turns off the optimization flags.
|
||||
# Adding optimization introduces certain risks, since for example not all optimizations performed by
|
||||
# ProGuard works on all versions of Dalvik. The following flags turn off various optimizations
|
||||
# known to have issues, but the list may not be complete or up to date. (The "arithmetic"
|
||||
# optimization can be used if you are only targeting Android 2.0 or later.) Make sure you test
|
||||
# thoroughly if you go this route.
|
||||
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||
-optimizationpasses 5
|
||||
-allowaccessmodification
|
||||
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-verbose
|
||||
|
||||
# Preserve some attributes that may be required for reflection.
|
||||
-keepattributes AnnotationDefault,
|
||||
EnclosingMethod,
|
||||
InnerClasses,
|
||||
RuntimeVisibleAnnotations,
|
||||
RuntimeVisibleParameterAnnotations,
|
||||
RuntimeVisibleTypeAnnotations,
|
||||
Signature
|
||||
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keep public class com.google.android.vending.licensing.ILicensingService
|
||||
-dontnote com.android.vending.licensing.ILicensingService
|
||||
-dontnote com.google.vending.licensing.ILicensingService
|
||||
-dontnote com.google.android.vending.licensing.ILicensingService
|
||||
|
||||
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# Keep setters in Views so that animations can still work.
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick.
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# Preserve annotated Javascript interface methods.
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# The support libraries contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
-dontnote android.support.**
|
||||
-dontnote androidx.**
|
||||
-dontwarn android.support.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
# This class is deprecated, but remains for backward compatibility.
|
||||
-dontwarn android.util.FloatMath
|
||||
|
||||
# Understand the @Keep support annotation.
|
||||
-keep class android.support.annotation.Keep
|
||||
-keep class androidx.annotation.Keep
|
||||
|
||||
-keep @android.support.annotation.Keep class * {*;}
|
||||
-keep @androidx.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
# These classes are duplicated between android.jar and org.apache.http.legacy.jar.
|
||||
-dontnote org.apache.http.**
|
||||
-dontnote android.net.http.**
|
||||
|
||||
# These classes are duplicated between android.jar and core-lambda-stubs.jar.
|
||||
-dontnote java.lang.invoke.**
|
||||
@@ -1,116 +0,0 @@
|
||||
# This is a configuration file for ProGuard.
|
||||
# http://proguard.sourceforge.net/index.html#manual/usage.html
|
||||
#
|
||||
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
|
||||
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
|
||||
# will be ignored by new version of the Android plugin for Gradle.
|
||||
|
||||
# Optimization is turned off by default. Dex does not like code run
|
||||
# through the ProGuard optimize steps (and performs some
|
||||
# of these optimizations on its own).
|
||||
# Note that if you want to enable optimization, you cannot just
|
||||
# include optimization flags in your own project configuration file;
|
||||
# instead you will need to point to the
|
||||
# "proguard-android-optimize.txt" file instead of this one from your
|
||||
# project.properties file.
|
||||
-dontoptimize
|
||||
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-verbose
|
||||
|
||||
# Preserve some attributes that may be required for reflection.
|
||||
-keepattributes AnnotationDefault,
|
||||
EnclosingMethod,
|
||||
InnerClasses,
|
||||
RuntimeVisibleAnnotations,
|
||||
RuntimeVisibleParameterAnnotations,
|
||||
RuntimeVisibleTypeAnnotations,
|
||||
Signature
|
||||
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keep public class com.google.android.vending.licensing.ILicensingService
|
||||
-dontnote com.android.vending.licensing.ILicensingService
|
||||
-dontnote com.google.vending.licensing.ILicensingService
|
||||
-dontnote com.google.android.vending.licensing.ILicensingService
|
||||
|
||||
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# Keep setters in Views so that animations can still work.
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick.
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# Preserve annotated Javascript interface methods.
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# The support libraries contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
-dontnote android.support.**
|
||||
-dontnote androidx.**
|
||||
-dontwarn android.support.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
# This class is deprecated, but remains for backward compatibility.
|
||||
-dontwarn android.util.FloatMath
|
||||
|
||||
# Understand the @Keep support annotation.
|
||||
-keep class android.support.annotation.Keep
|
||||
-keep class androidx.annotation.Keep
|
||||
|
||||
-keep @android.support.annotation.Keep class * {*;}
|
||||
-keep @androidx.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
# These classes are duplicated between android.jar and org.apache.http.legacy.jar.
|
||||
-dontnote org.apache.http.**
|
||||
-dontnote android.net.http.**
|
||||
|
||||
# These classes are duplicated between android.jar and core-lambda-stubs.jar.
|
||||
-dontnote java.lang.invoke.**
|
||||
@@ -1,117 +0,0 @@
|
||||
# This is a configuration file for ProGuard.
|
||||
# http://proguard.sourceforge.net/index.html#manual/usage.html
|
||||
#
|
||||
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
|
||||
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
|
||||
# will be ignored by new version of the Android plugin for Gradle.
|
||||
|
||||
# Optimizations can be turned on and off in the 'postProcessing' DSL block.
|
||||
# The configuration below is applied if optimizations are enabled.
|
||||
# Adding optimization introduces certain risks, since for example not all optimizations performed by
|
||||
# ProGuard works on all versions of Dalvik. The following flags turn off various optimizations
|
||||
# known to have issues, but the list may not be complete or up to date. (The "arithmetic"
|
||||
# optimization can be used if you are only targeting Android 2.0 or later.) Make sure you test
|
||||
# thoroughly if you go this route.
|
||||
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||
-optimizationpasses 5
|
||||
-allowaccessmodification
|
||||
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-verbose
|
||||
|
||||
# Preserve some attributes that may be required for reflection.
|
||||
-keepattributes AnnotationDefault,
|
||||
EnclosingMethod,
|
||||
InnerClasses,
|
||||
RuntimeVisibleAnnotations,
|
||||
RuntimeVisibleParameterAnnotations,
|
||||
RuntimeVisibleTypeAnnotations,
|
||||
Signature
|
||||
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keep public class com.google.android.vending.licensing.ILicensingService
|
||||
-dontnote com.android.vending.licensing.ILicensingService
|
||||
-dontnote com.google.vending.licensing.ILicensingService
|
||||
-dontnote com.google.android.vending.licensing.ILicensingService
|
||||
|
||||
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# Keep setters in Views so that animations can still work.
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick.
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# Preserve annotated Javascript interface methods.
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# The support libraries contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
-dontnote android.support.**
|
||||
-dontnote androidx.**
|
||||
-dontwarn android.support.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
# This class is deprecated, but remains for backward compatibility.
|
||||
-dontwarn android.util.FloatMath
|
||||
|
||||
# Understand the @Keep support annotation.
|
||||
-keep class android.support.annotation.Keep
|
||||
-keep class androidx.annotation.Keep
|
||||
|
||||
-keep @android.support.annotation.Keep class * {*;}
|
||||
-keep @androidx.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@androidx.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
# These classes are duplicated between android.jar and org.apache.http.legacy.jar.
|
||||
-dontnote org.apache.http.**
|
||||
-dontnote android.net.http.**
|
||||
|
||||
# These classes are duplicated between android.jar and core-lambda-stubs.jar.
|
||||
-dontnote java.lang.invoke.**
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
8
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user