diff --git a/backend/packages/common/src/index.ts b/backend/packages/common/src/index.ts index 963ebb6..4972a34 100644 --- a/backend/packages/common/src/index.ts +++ b/backend/packages/common/src/index.ts @@ -8,6 +8,9 @@ export * from './decorators/roles.decorator'; export * from './guards/jwt-auth.guard'; export * from './guards/roles.guard'; +// Strategies +export * from './strategies/jwt.strategy'; + // Interceptors export * from './interceptors/logging.interceptor'; export * from './interceptors/transform.interceptor'; diff --git a/backend/packages/common/src/strategies/jwt.strategy.ts b/backend/packages/common/src/strategies/jwt.strategy.ts new file mode 100644 index 0000000..ee86497 --- /dev/null +++ b/backend/packages/common/src/strategies/jwt.strategy.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; + +/** + * Shared JWT Passport Strategy. + * Register as a provider in each service module that uses JwtAuthGuard. + * + * Usage in module: + * providers: [JwtStrategy, ...] + */ +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: process.env.JWT_ACCESS_SECRET || 'dev-access-secret', + }); + } + + async validate(payload: { sub: string; role: string; kycLevel: number; type: string }) { + return { sub: payload.sub, role: payload.role, kycLevel: payload.kycLevel }; + } +} diff --git a/backend/services/clearing-service/src/clearing.module.ts b/backend/services/clearing-service/src/clearing.module.ts index dfe49c7..ddd988b 100644 --- a/backend/services/clearing-service/src/clearing.module.ts +++ b/backend/services/clearing-service/src/clearing.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; +import { JwtStrategy } from '@genex/common'; // Domain entities import { Settlement } from './domain/entities/settlement.entity'; @@ -44,6 +45,7 @@ import { AdminReportsController } from './interface/http/controllers/admin-repor ], controllers: [ClearingController, AdminFinanceController, AdminReportsController], providers: [ + JwtStrategy, // Repository DI bindings (interface -> implementation) { provide: SETTLEMENT_REPOSITORY, useClass: SettlementRepository }, { provide: REFUND_REPOSITORY, useClass: RefundRepository }, diff --git a/backend/services/compliance-service/src/compliance.module.ts b/backend/services/compliance-service/src/compliance.module.ts index 4596205..c3fae88 100644 --- a/backend/services/compliance-service/src/compliance.module.ts +++ b/backend/services/compliance-service/src/compliance.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; +import { JwtStrategy } from '@genex/common'; // ─── Domain Entities ─── import { AmlAlert } from './domain/entities/aml-alert.entity'; @@ -75,6 +76,7 @@ import { AdminInsuranceController } from './interface/http/controllers/admin-ins AdminInsuranceController, ], providers: [ + JwtStrategy, // ─── Repository DI bindings (interface → implementation) ─── { provide: AML_ALERT_REPOSITORY, useClass: AmlAlertRepository }, { provide: OFAC_SCREENING_REPOSITORY, useClass: OfacScreeningRepository }, diff --git a/backend/services/issuer-service/src/issuer.module.ts b/backend/services/issuer-service/src/issuer.module.ts index abf5247..e2e9b2f 100644 --- a/backend/services/issuer-service/src/issuer.module.ts +++ b/backend/services/issuer-service/src/issuer.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; +import { JwtStrategy } from '@genex/common'; // Domain entities import { Issuer } from './domain/entities/issuer.entity'; @@ -88,6 +89,7 @@ import { RolesGuard } from './interface/http/guards/roles.guard'; CouponBatchController, ], providers: [ + JwtStrategy, // Infrastructure -> Domain port binding (Repository pattern) { provide: ISSUER_REPOSITORY, useClass: IssuerRepository }, { provide: COUPON_REPOSITORY, useClass: CouponRepository }, diff --git a/backend/services/notification-service/src/notification.module.ts b/backend/services/notification-service/src/notification.module.ts index 789036e..b43f2be 100644 --- a/backend/services/notification-service/src/notification.module.ts +++ b/backend/services/notification-service/src/notification.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; +import { JwtStrategy } from '@genex/common'; // Domain entities import { Notification } from './domain/entities/notification.entity'; @@ -94,6 +95,7 @@ import { DeviceTokenController } from './interface/http/controllers/device-token DeviceTokenController, ], providers: [ + JwtStrategy, // Infrastructure -> Domain repository binding { provide: NOTIFICATION_REPOSITORY, useClass: NotificationRepository }, { provide: ANNOUNCEMENT_REPOSITORY, useClass: AnnouncementRepositoryImpl }, diff --git a/backend/services/telemetry-service/src/telemetry.module.ts b/backend/services/telemetry-service/src/telemetry.module.ts index f40346e..ff407db 100644 --- a/backend/services/telemetry-service/src/telemetry.module.ts +++ b/backend/services/telemetry-service/src/telemetry.module.ts @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { ScheduleModule } from '@nestjs/schedule'; +import { JwtStrategy } from '@genex/common'; // Domain entities import { TelemetryEvent } from './domain/entities/telemetry-event.entity'; @@ -72,6 +73,7 @@ import { HealthController } from './interface/http/controllers/health.controller HealthController, ], providers: [ + JwtStrategy, { provide: TELEMETRY_EVENT_REPOSITORY, useClass: TelemetryEventRepository }, { provide: ONLINE_SNAPSHOT_REPOSITORY, useClass: OnlineSnapshotRepository }, { provide: DAILY_ACTIVE_STATS_REPOSITORY, useClass: DailyActiveStatsRepository }, diff --git a/backend/services/user-service/src/user.module.ts b/backend/services/user-service/src/user.module.ts index 65c275a..59d147f 100644 --- a/backend/services/user-service/src/user.module.ts +++ b/backend/services/user-service/src/user.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; +import { JwtStrategy } from '@genex/common'; // Domain entities import { User } from './domain/entities/user.entity'; @@ -59,6 +60,7 @@ import { AdminAnalyticsController } from './interface/http/controllers/admin-ana AdminDashboardController, AdminUserController, AdminSystemController, AdminAnalyticsController, ], providers: [ + JwtStrategy, // Infrastructure -> Domain port binding { provide: USER_REPOSITORY, useClass: UserRepository }, { provide: KYC_REPOSITORY, useClass: KycRepository }, diff --git a/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart b/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart index c763e38..a17aff9 100644 --- a/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart +++ b/frontend/genex-mobile/lib/features/auth/presentation/pages/welcome_page.dart @@ -78,7 +78,12 @@ class _WelcomePageState extends ConsumerState { // ── 微信 ───────────────────────────────────────────────────────────────── Future _onWechatTap() async { - final installed = await _fluwx.isWeChatInstalled; + bool installed = false; + try { + installed = await _fluwx.isWeChatInstalled; + } catch (_) { + // WxApi not configured (WECHAT_APP_ID not provided) — treat as not installed + } if (!installed) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(