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
48 changes: 48 additions & 0 deletions .detoxrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/** @type {Detox.DetoxConfig} */
module.exports = {
testRunner: {
args: {
$0: 'jest',
config: 'jest.config.e2e.js',
},
jest: {
setupTimeout: 120000,
},
},

apps: {
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build:
'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
reversePorts: [8081],
},
},

devices: {
'emulator': {
type: 'android.emulator',
device: {
avdName: 'Pixel_7',
},
},
'attached.device': {
type: 'android.attached',
device: {
adbName: '.*', // matches any USB-attached device
},
},
},

configurations: {
'android.emu.debug': {
device: 'emulator',
app: 'android.debug',
},
'android.device.debug': {
device: 'attached.device',
app: 'android.debug',
},
},
};
6 changes: 5 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ android {

packagingOptions {
jniLibs {
useLegacyPackaging false
useLegacyPackaging true
}
pickFirst '**/libcrypto.so'
}

androidResources {
Expand Down Expand Up @@ -92,6 +93,9 @@ dependencies {
implementation("org.tensorflow:tensorflow-lite:2.16.1")
implementation("org.tensorflow:tensorflow-lite-select-tf-ops:2.16.1")

// Required by op-sqlite SQLCipher mode — provides libcrypto.so native library
implementation("com.android.ndk.thirdparty:openssl:1.1.1q-beta-1")

if (hermesEnabled) {
implementation("com.facebook.react:hermes-android")
} else {
Expand Down
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<application
android:name=".MainApplication"
android:allowBackup="false"
android:extractNativeLibs="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
Expand Down
12 changes: 10 additions & 2 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8
org.gradle.parallel=true
android.useAndroidX=true
android.enableJetifier=true

newArchEnabled=false
hermesEnabled=true

reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
reactNativeArchitectures=arm64-v8a

# ── VisionCamera / WorkletsCore linker fix ──────────────────────────────────
# Disables Frame Processors in react-native-vision-camera's own Gradle/CMake
# to prevent undefined symbol errors for RNWorklet::JsiWorkletContext.
# The app uses its own JNI bridge (offline_face_auth_jni) for frame processing
# and does NOT depend on VisionCamera's prefab-linked rnworklets path.
VisionCamera_disableFrameProcessors=true

kotlin.code.style=official
Binary file added crash.txt
Binary file not shown.
97 changes: 95 additions & 2 deletions deploy/aws/cloudformation/template.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,95 @@
# Path: OfflineFaceAuth/deploy/aws/cloudformation/template.yml
# Purpose: CloudFormation template provisioning S3 bucket, Lambda functions, IAM roles, API Gateway for sync endpoint.
AWSTemplateFormatVersion: "2010-09-09"
Description: >
Nayan OfflineFaceAuth – AWS infrastructure: S3 bucket, Lambda functions (authorizer + sync-handler),
IAM execution roles, and API Gateway endpoint for attendance sync.

Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
LambdaMemorySize:
Type: Number
Default: 256

Resources:

AttendanceBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "nayan-attendance-sync-${Environment}"
VersioningConfiguration:
Status: Enabled
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
LifecycleConfiguration:
Rules:
- Id: TransitionToGlacier
Status: Enabled
Transitions:
- TransitionInDays: 90
StorageClass: GLACIER
ExpirationInDays: 365

LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "NayanLambdaExecutionRole-${Environment}"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: S3AccessPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:HeadObject
Resource: !Sub "arn:aws:s3:::nayan-attendance-sync-${Environment}/*"

AuthorizerFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "NayanAuthorizer-${Environment}"
Runtime: nodejs18.x
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
MemorySize: !Ref LambdaMemorySize
Timeout: 10
Environment:
Variables:
ATTENDANCE_BUCKET_NAME: !Ref AttendanceBucket
NODE_ENV: !Ref Environment

SyncHandlerFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub "NayanSyncHandler-${Environment}"
Runtime: nodejs18.x
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
MemorySize: !Ref LambdaMemorySize
Timeout: 30
Environment:
Variables:
ATTENDANCE_BUCKET_NAME: !Ref AttendanceBucket
NODE_ENV: !Ref Environment

Outputs:
BucketName:
Value: !Ref AttendanceBucket
AuthorizerFunctionArn:
Value: !GetAtt AuthorizerFunction.Arn
SyncHandlerFunctionArn:
Value: !GetAtt SyncHandlerFunction.Arn
91 changes: 89 additions & 2 deletions deploy/aws/lambda/authorizer/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,89 @@
# Path: OfflineFaceAuth/deploy/aws/lambda/authorizer/index.js
# Purpose: Lambda authorizer generating pre-signed S3 URLs for multipart upload, validating device authentication tokens.
'use strict';

/**
* Lambda Authorizer – Pre-signed S3 URL Generator
* Path: deploy/aws/lambda/authorizer/index.js
*
* Validates device authentication tokens and generates pre-signed S3 URLs
* for multipart upload of attendance data batches.
*/

const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

const BUCKET_NAME = process.env.ATTENDANCE_BUCKET_NAME || 'nayan-attendance-sync';
const PRESIGN_EXPIRES_IN = 300; // 5 minutes

const s3 = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });

/**
* Validates the device token from the Authorization header.
* @param {string} token - Bearer token from the device.
* @returns {boolean} Whether the token is valid.
*/
function validateDeviceToken(token) {
if (!token || !token.startsWith('Bearer ')) {
return false;
}
// NOTE: Replace with real JWT/HMAC validation in production.
const jwt = token.replace('Bearer ', '');
return jwt.length > 16;
}
Comment on lines +24 to +31

/**
* Lambda handler – generates a pre-signed PUT URL for attendance batch upload.
* @param {import('aws-lambda').APIGatewayProxyEvent} event
* @returns {Promise<import('aws-lambda').APIGatewayProxyResult>}
*/
exports.handler = async (event) => {
try {
const authHeader = event.headers?.Authorization || event.headers?.authorization;

if (!validateDeviceToken(authHeader)) {
return {
statusCode: 401,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Unauthorized: invalid or missing device token' }),
};
}

const deviceId = event.queryStringParameters?.deviceId;
if (!deviceId) {
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Missing required query parameter: deviceId' }),
};
}

const objectKey = `attendance/${deviceId}/${Date.now()}.ndjson`;

const command = new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: objectKey,
ContentType: 'application/x-ndjson',
ServerSideEncryption: 'AES256',
});

const presignedUrl = await getSignedUrl(s3, command, {
expiresIn: PRESIGN_EXPIRES_IN,
});

return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uploadUrl: presignedUrl,
objectKey,
expiresIn: PRESIGN_EXPIRES_IN,
}),
};
} catch (error) {
console.error('[Authorizer] Error generating pre-signed URL:', error);
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Internal server error' }),
};
}
};
19 changes: 17 additions & 2 deletions deploy/aws/lambda/authorizer/package.json
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
# Path: OfflineFaceAuth/deploy/aws/lambda/authorizer/package.json
# Purpose: Authorizer Lambda dependencies with aws-sdk for S3 pre-signed URL generation.
{
"name": "nayan-authorizer",
"version": "1.0.0",
"description": "Lambda authorizer generating pre-signed S3 URLs for multipart upload, validating device authentication tokens.",
"main": "index.js",
"private": true,
"scripts": {
"deploy": "zip -r authorizer.zip . && aws lambda update-function-code --function-name NayanAuthorizer --zip-file fileb://authorizer.zip"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.583.0",
"@aws-sdk/s3-request-presigner": "^3.583.0"
},
"engines": {
"node": ">=18"
}
}
Loading