Skip to content

Commit f2e9fd8

Browse files
Merge branch 'main' into rebranding-auth
2 parents 01ec32b + f63333e commit f2e9fd8

File tree

36 files changed

+777
-146
lines changed

36 files changed

+777
-146
lines changed

.github/workflows/build-ios.yml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: iOS Build and upload to testflight
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
build:
11+
runs-on: macos-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version-file: "frontend/package.json"
21+
22+
- name: Setup Xcode
23+
uses: maxim-lobanov/setup-xcode@v1
24+
with:
25+
xcode-version: '16.2'
26+
27+
- name: Install Bundler
28+
run: gem install bundler
29+
30+
- name: Create .env file
31+
working-directory: frontend
32+
run: |
33+
echo "VITE_BUILD_DATE=1970-01-01
34+
VITE_BUILD_TIME=00:00:00
35+
VITE_BUILD_TS=1970-01-01T00:00:00+0000
36+
VITE_BUILD_COMMIT_SHA=test
37+
VITE_BUILD_ENV_CODE=test
38+
VITE_BUILD_WORKFLOW_RUNNER=test
39+
VITE_BUILD_WORKFLOW_NAME=test
40+
VITE_BUILD_WORKFLOW_RUN_NUMBER=1
41+
VITE_BUILD_WORKFLOW_RUN_ATTEMPT=1
42+
VITE_BASE_URL_API=https://jsonplaceholder.typicode.com
43+
VITE_TOAST_AUTO_DISMISS_MILLIS=1500" > .env
44+
45+
- name: Install dependencies
46+
working-directory: frontend
47+
run: npm ci
48+
49+
- name: Build Ionic
50+
working-directory: frontend
51+
run: npm run build
52+
53+
- name: Prepare Capacitor for iOS
54+
working-directory: frontend
55+
run: |
56+
npx cap sync ios
57+
npx cap copy ios
58+
59+
- name: Install CocoaPods
60+
working-directory: frontend/ios/App
61+
run: pod install --verbose
62+
63+
- name: Install Fastlane
64+
working-directory: frontend/ios/App
65+
run: gem install fastlane
66+
67+
- name: Setup SSH for Fastlane Match Repo Access
68+
run: |
69+
mkdir -p ~/.ssh
70+
echo "${{ secrets.IOS_SIGNING_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
71+
chmod 600 ~/.ssh/id_rsa
72+
ssh-keyscan github.com >> ~/.ssh/known_hosts
73+
74+
- name: Decode App Store API Key
75+
working-directory: frontend/ios/App
76+
env:
77+
IOS_API_KEY_JSON: ${{ secrets.IOS_API_KEY_JSON }}
78+
run: echo "$IOS_API_KEY_JSON" > /tmp/api-key.json
79+
80+
- name: Set up Fastlane Match Password
81+
run: echo "MATCH_PASSWORD=${{ secrets.FASTLANE_MATCH_PASSWORD }}" >> $GITHUB_ENV
82+
83+
- name: Build iOS App for Simulator
84+
working-directory: frontend/ios/App
85+
env:
86+
NSUnbufferedIO: "YES"
87+
FASTLANE_VERBOSE: "1"
88+
run: |
89+
bundle install
90+
bundle exec fastlane ios build_for_simulator --verbose
91+
92+
- name: Upload App.app (iOS Simulator File)
93+
uses: actions/upload-artifact@v4
94+
with:
95+
name: build_for_simulator
96+
path: frontend/ios/App/build/simulator/App.xcarchive/Products/Applications/
97+
retention-days: 7
98+
99+
- name: Build iOS App for testflight.
100+
working-directory: frontend/ios/App
101+
env:
102+
NSUnbufferedIO: "YES"
103+
FASTLANE_VERBOSE: "1"
104+
FASTLANE_MATCH_PASSWORD: ${{ secrets.FASTLANE_MATCH_PASSWORD }}
105+
run: |
106+
bundle install
107+
bundle exec fastlane ios build_and_send_to_testflight --verbose
108+
109+
- name: Upload App.ipa (iOS device installation)
110+
uses: actions/upload-artifact@v4
111+
with:
112+
name: build_for_device
113+
path: frontend/ios/App/build/device/App.ipa
114+
retention-days: 7

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ node_modules
1717
*.ntvs*
1818
*.njsproj
1919
*.sln
20-
*.sw*
2120

2221
# Use either yarn.lock or package-lock.json
2322
# Uncomment one of them to maintain a single lockfile

backend/src/app.module.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Module, NestModule } from '@nestjs/common';
1+
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
22
import { ConfigModule } from '@nestjs/config';
33
import configuration from './config/configuration';
44
import { AppController } from './app.controller';
@@ -9,6 +9,8 @@ import { PerplexityController } from './controllers/perplexity/perplexity.contro
99
import { UserController } from './user/user.controller';
1010
import { ReportsModule } from './reports/reports.module';
1111
import { HealthController } from './health/health.controller';
12+
import { AuthMiddleware } from './auth/auth.middleware';
13+
1214
@Module({
1315
imports: [
1416
ConfigModule.forRoot({
@@ -21,8 +23,7 @@ import { HealthController } from './health/health.controller';
2123
providers: [AppService, AwsSecretsService, PerplexityService],
2224
})
2325
export class AppModule implements NestModule {
24-
configure() {
25-
// Add your middleware configuration here if needed
26-
// If you don't need middleware, you can leave this empty
26+
configure(consumer: MiddlewareConsumer) {
27+
consumer.apply(AuthMiddleware).forRoutes('*'); // Apply to all routes
2728
}
2829
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Injectable, NestMiddleware } from '@nestjs/common';
2+
import { Request, Response, NextFunction } from 'express';
3+
import { ConfigService } from '@nestjs/config';
4+
import * as jwt from 'jsonwebtoken';
5+
6+
// Extend the Express Request interface to include the user property
7+
export interface RequestWithUser extends Request {
8+
user?: {
9+
sub: string;
10+
email?: string;
11+
groups?: string[];
12+
[key: string]: any;
13+
} | null;
14+
}
15+
16+
// Add this interface to define the token structure
17+
interface DecodedToken {
18+
payload: {
19+
sub: string;
20+
username?: string;
21+
email?: string;
22+
[key: string]: any;
23+
};
24+
header: any;
25+
signature: string;
26+
}
27+
28+
@Injectable()
29+
export class AuthMiddleware implements NestMiddleware {
30+
constructor(private configService: ConfigService) {}
31+
32+
use(req: RequestWithUser, res: Response, next: NextFunction) {
33+
const authHeader = req.headers.authorization;
34+
if (authHeader && authHeader.startsWith('Bearer ')) {
35+
const token = authHeader.substring(7);
36+
try {
37+
// Verify the JWT token
38+
const decodedToken = jwt.decode(token, { complete: true }) as DecodedToken;
39+
40+
// Access user info from the payload
41+
req.user = {
42+
sub: decodedToken?.payload.sub as string,
43+
username: decodedToken?.payload.username as string,
44+
};
45+
} catch (error) {
46+
// If token verification fails, set user to null
47+
console.log('AuthMiddleware error');
48+
console.log(error);
49+
req.user = null;
50+
}
51+
} else {
52+
// No token provided
53+
req.user = null;
54+
}
55+
56+
next();
57+
}
58+
}

backend/src/iac/backend-stack.ts

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -445,12 +445,62 @@ export class BackendStack extends cdk.Stack {
445445

446446
// Add CORS to all resources
447447
api.root.addCorsPreflight(corsOptions);
448-
apiResource.addCorsPreflight(corsOptions);
449-
reportsResource.addCorsPreflight(corsOptions);
450-
latestResource.addCorsPreflight(corsOptions);
451-
reportIdResource.addCorsPreflight(corsOptions);
452-
reportStatusResource.addCorsPreflight(corsOptions);
453-
docsResource.addCorsPreflight(corsOptions);
448+
apiResource.addCorsPreflight({
449+
...corsOptions,
450+
allowCredentials: false, // This is crucial - make sure OPTIONS requests don't require credentials
451+
});
452+
reportsResource.addCorsPreflight({
453+
...corsOptions,
454+
allowCredentials: false,
455+
});
456+
latestResource.addCorsPreflight({
457+
...corsOptions,
458+
allowCredentials: false,
459+
});
460+
reportIdResource.addCorsPreflight({
461+
...corsOptions,
462+
allowCredentials: false,
463+
});
464+
reportStatusResource.addCorsPreflight({
465+
...corsOptions,
466+
allowCredentials: false,
467+
});
468+
docsResource.addCorsPreflight({
469+
...corsOptions,
470+
allowCredentials: false,
471+
});
472+
473+
// Configure Gateway Responses to add CORS headers to error responses
474+
const gatewayResponseTypes = [
475+
apigateway.ResponseType.UNAUTHORIZED,
476+
apigateway.ResponseType.ACCESS_DENIED,
477+
apigateway.ResponseType.DEFAULT_4XX,
478+
apigateway.ResponseType.DEFAULT_5XX,
479+
apigateway.ResponseType.RESOURCE_NOT_FOUND,
480+
apigateway.ResponseType.MISSING_AUTHENTICATION_TOKEN,
481+
apigateway.ResponseType.INVALID_API_KEY,
482+
apigateway.ResponseType.THROTTLED,
483+
apigateway.ResponseType.INTEGRATION_FAILURE,
484+
apigateway.ResponseType.INTEGRATION_TIMEOUT,
485+
];
486+
487+
gatewayResponseTypes.forEach(responseType => {
488+
new apigateway.CfnGatewayResponse(
489+
this,
490+
`${appName}GatewayResponse-${responseType.responseType.toString()}-${props.environment}`,
491+
{
492+
restApiId: api.restApiId,
493+
responseType: responseType.responseType.toString(),
494+
responseParameters: {
495+
'gatewayresponse.header.Access-Control-Allow-Origin': "'*'",
496+
'gatewayresponse.header.Access-Control-Allow-Headers':
497+
"'Content-Type,Authorization,X-Amz-Date,X-Api-Key'",
498+
'gatewayresponse.header.Access-Control-Allow-Methods':
499+
"'GET,POST,PUT,PATCH,DELETE,OPTIONS'",
500+
},
501+
},
502+
);
503+
});
454504

455505
// Create API Gateway execution role with required permissions
456506
new iam.Role(this, `${appName}APIGatewayRole-${props.environment}`, {

backend/src/iac/update-api-policy.js

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,29 +62,36 @@ async function main() {
6262
const policy = {
6363
Version: '2012-10-17',
6464
Statement: [
65-
// Allow authenticated Cognito users
6665
{
67-
Effect: 'Allow',
68-
Principal: '*',
69-
Action: 'execute-api:Invoke',
70-
Resource: `arn:aws:execute-api:${REGION}:*:${api.id}/*/*`,
71-
Condition: {
72-
StringEquals: {
73-
'cognito-identity.amazonaws.com:aud': cognitoUserPoolId
66+
"Version": "2012-10-17",
67+
"Statement": [
68+
// Allow OPTIONS requests
69+
{
70+
"Effect": "Allow",
71+
"Principal": "*",
72+
"Action": "execute-api:Invoke",
73+
"Resource": "arn:aws:execute-api:us-east-1:*:xhvwo6wp66/*/OPTIONS/*"
74+
},
75+
{
76+
// Allow all other requests - authentication will be handled by Cognito
77+
"Effect": "Allow",
78+
"Principal": "*",
79+
"Action": "execute-api:Invoke",
80+
"Resource": "arn:aws:execute-api:us-east-1:*:xhvwo6wp66/*/*"
81+
},
82+
{
83+
// Deny non-HTTPS requests
84+
"Effect": "Deny",
85+
"Principal": "*",
86+
"Action": "execute-api:Invoke",
87+
"Resource": "arn:aws:execute-api:us-east-1:*:xhvwo6wp66/*/*",
88+
"Condition": {
89+
"Bool": {
90+
"aws:SecureTransport": "false"
91+
}
92+
}
7493
}
75-
}
76-
},
77-
// Deny non-HTTPS requests
78-
{
79-
Effect: 'Deny',
80-
Principal: '*',
81-
Action: 'execute-api:Invoke',
82-
Resource: `arn:aws:execute-api:${REGION}:*:${api.id}/*/*`,
83-
Condition: {
84-
Bool: {
85-
'aws:SecureTransport': 'false'
86-
}
87-
}
94+
]
8895
}
8996
]
9097
};

backend/src/main.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ async function bootstrap() {
1313
// Enable CORS
1414
app.enableCors({
1515
origin: [
16-
'http://localhost:5173', // Vite default dev server
16+
'http://localhost:5173',
1717
'http://localhost:3000',
18-
'http://localhost:4173', // Vite preview
18+
'http://localhost:4173',
19+
'https://localhost', // Add this for Capacitor
1920
...(process.env.FRONTEND_URL ? [process.env.FRONTEND_URL] : []),
2021
],
2122
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',

0 commit comments

Comments
 (0)