fix:页面和node后端ok;
This commit is contained in:
parent
ca7e92a1a1
commit
3a014a3d20
@ -32,14 +32,47 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
|
||||
const isAuthenticated = !!user
|
||||
|
||||
const refreshTokens = async (): Promise<boolean> => {
|
||||
const refreshToken = localStorage.getItem("refreshToken")
|
||||
if (!refreshToken) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("http://localhost:3001/api/v1/auth/refresh", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
localStorage.removeItem("accessToken")
|
||||
localStorage.removeItem("refreshToken")
|
||||
return false
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
localStorage.setItem("accessToken", data.accessToken)
|
||||
localStorage.setItem("refreshToken", data.refreshToken)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error("Failed to refresh tokens:", error)
|
||||
localStorage.removeItem("accessToken")
|
||||
localStorage.removeItem("refreshToken")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchUserProfile = async (): Promise<User | null> => {
|
||||
const token = localStorage.getItem("accessToken")
|
||||
let token = localStorage.getItem("accessToken")
|
||||
if (!token) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("http://localhost:3000/api/v1/auth/profile", {
|
||||
let response = await fetch("http://localhost:3001/api/v1/auth/profile", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
@ -47,8 +80,29 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
},
|
||||
})
|
||||
|
||||
// If token is expired, try to refresh
|
||||
if (response.status === 401) {
|
||||
const refreshed = await refreshTokens()
|
||||
if (!refreshed) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Retry with new token
|
||||
token = localStorage.getItem("accessToken")
|
||||
if (!token) {
|
||||
return null
|
||||
}
|
||||
|
||||
response = await fetch("http://localhost:3001/api/v1/auth/profile", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
// Token might be invalid, remove it
|
||||
localStorage.removeItem("accessToken")
|
||||
localStorage.removeItem("refreshToken")
|
||||
return null
|
||||
@ -63,7 +117,6 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
hasActiveSubscription: profileData.hasActiveSubscription,
|
||||
}
|
||||
} catch (error) {
|
||||
// Network error or other issue
|
||||
console.error("Failed to fetch user profile:", error)
|
||||
return null
|
||||
}
|
||||
@ -72,7 +125,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
const login = async (email: string, password: string) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const response = await fetch("http://localhost:3000/api/v1/auth/login-email", {
|
||||
const response = await fetch("http://localhost:3001/api/v1/auth/login-email", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@ -113,7 +166,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
const register = async (email: string, password: string, displayName: string) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const response = await fetch("http://localhost:3000/api/v1/auth/register-email", {
|
||||
const response = await fetch("http://localhost:3001/api/v1/auth/register-email", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@ -7,7 +7,7 @@ export const devicesApi = {
|
||||
throw new Error("No access token found")
|
||||
}
|
||||
|
||||
const response = await fetch("http://localhost:3000/api/v1/devices", {
|
||||
const response = await fetch("http://localhost:3001/api/v1/devices", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
|
||||
@ -31,7 +31,7 @@ export const eventsApi = {
|
||||
throw new Error("No access token found")
|
||||
}
|
||||
|
||||
const url = new URL("http://localhost:3000/api/v1/events")
|
||||
const url = new URL("http://localhost:3001/api/v1/events")
|
||||
|
||||
if (params.limit) {
|
||||
url.searchParams.append("limit", params.limit.toString())
|
||||
@ -69,7 +69,7 @@ export const eventsApi = {
|
||||
throw new Error("No access token found")
|
||||
}
|
||||
|
||||
const response = await fetch(`http://localhost:3000/api/v1/events/${eventId}`, {
|
||||
const response = await fetch(`http://localhost:3001/api/v1/events/${eventId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
|
||||
@ -37,7 +37,7 @@ export const subscriptionApi = {
|
||||
throw new Error("No access token found")
|
||||
}
|
||||
|
||||
const response = await fetch("http://localhost:3000/api/v1/payments/subscription", {
|
||||
const response = await fetch("http://localhost:3001/api/v1/payments/subscription", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
@ -61,7 +61,7 @@ export const subscriptionApi = {
|
||||
throw new Error("No access token found")
|
||||
}
|
||||
|
||||
const response = await fetch("http://localhost:3000/api/v1/payments/customer-portal", {
|
||||
const response = await fetch("http://localhost:3001/api/v1/payments/customer-portal", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
@ -86,7 +86,7 @@ export const subscriptionApi = {
|
||||
throw new Error("No access token found")
|
||||
}
|
||||
|
||||
const response = await fetch("http://localhost:3000/api/v1/payments/checkout-session/stripe", {
|
||||
const response = await fetch("http://localhost:3001/api/v1/payments/checkout-session/stripe", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
UseGuards,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
import { RegisterEmailDto } from './dto/register-email.dto';
|
||||
@ -24,7 +25,7 @@ export class AuthController {
|
||||
|
||||
@Post('register-email')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async registerWithEmail(@Body(ValidationPipe) registerDto: RegisterEmailDto) {
|
||||
async registerWithEmail(@Body() registerDto: RegisterEmailDto) {
|
||||
try {
|
||||
const result = await this.authService.registerWithEmail(registerDto);
|
||||
this.metricsService.recordAuthOperation('register', true, 'email');
|
||||
@ -37,7 +38,7 @@ export class AuthController {
|
||||
|
||||
@Post('login-email')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async loginWithEmail(@Body(ValidationPipe) loginDto: LoginEmailDto) {
|
||||
async loginWithEmail(@Body() loginDto: LoginEmailDto) {
|
||||
try {
|
||||
const result = await this.authService.loginWithEmail(loginDto);
|
||||
this.metricsService.recordAuthOperation('login', true, 'email');
|
||||
@ -54,4 +55,13 @@ export class AuthController {
|
||||
const userId = req.user.userId;
|
||||
return await this.authService.getUserProfile(userId);
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async refreshToken(@Body('refreshToken') refreshToken: string) {
|
||||
if (!refreshToken) {
|
||||
throw new UnauthorizedException('Refresh token is required');
|
||||
}
|
||||
return await this.authService.refreshToken(refreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,17 +8,21 @@ import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
import { UserProfile } from '../entities/user-profile.entity';
|
||||
import { UserIdentity } from '../entities/user-identity.entity';
|
||||
import { PaymentsModule } from '../payments/payments.module';
|
||||
import { MetricsModule } from '../metrics/metrics.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([UserProfile, UserIdentity]),
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret:
|
||||
process.env.JWT_ACCESS_SECRET || 'default-secret-change-in-production',
|
||||
signOptions: { expiresIn: process.env.JWT_ACCESS_EXPIRATION || '15m' },
|
||||
JwtModule.registerAsync({
|
||||
useFactory: () => ({
|
||||
secret:
|
||||
process.env.JWT_ACCESS_SECRET || 'default-secret-change-in-production',
|
||||
signOptions: { expiresIn: process.env.JWT_ACCESS_EXPIRATION || '2h' },
|
||||
}),
|
||||
}),
|
||||
forwardRef(() => PaymentsModule),
|
||||
MetricsModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
|
||||
@ -184,4 +184,55 @@ export class AuthService {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<{
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
}> {
|
||||
try {
|
||||
// Verify the refresh token
|
||||
const payload = this.jwtService.verify(refreshToken, {
|
||||
secret: process.env.JWT_REFRESH_SECRET || 'default-refresh-secret',
|
||||
});
|
||||
|
||||
// Check if user still exists
|
||||
const userProfile = await this.userProfileRepository.findOne({
|
||||
where: { id: payload.userId },
|
||||
relations: ['identities'],
|
||||
});
|
||||
|
||||
if (!userProfile) {
|
||||
throw new UnauthorizedException('User not found');
|
||||
}
|
||||
|
||||
// Get the email from user identity
|
||||
const emailIdentity = userProfile.identities.find(
|
||||
(identity) => identity.provider === 'email',
|
||||
);
|
||||
|
||||
if (!emailIdentity) {
|
||||
throw new UnauthorizedException('User email not found');
|
||||
}
|
||||
|
||||
// Generate new tokens
|
||||
const newPayload = {
|
||||
userId: userProfile.id,
|
||||
email: emailIdentity.email,
|
||||
sub: userProfile.id,
|
||||
};
|
||||
|
||||
const newAccessToken = this.jwtService.sign(newPayload);
|
||||
const newRefreshToken = this.jwtService.sign(newPayload, {
|
||||
secret: process.env.JWT_REFRESH_SECRET || 'default-refresh-secret',
|
||||
expiresIn: process.env.JWT_REFRESH_EXPIRATION || '7d',
|
||||
});
|
||||
|
||||
return {
|
||||
accessToken: newAccessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Invalid refresh token');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,5 +22,5 @@ export class RegisterEmailDto {
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: 'Display name is required' })
|
||||
displayName?: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { DevicesController } from './devices.controller';
|
||||
import { DevicesService } from './devices.service';
|
||||
import { Device } from '../entities/device.entity';
|
||||
import { InventoryDevice } from '../entities/inventory-device.entity';
|
||||
import { UserProfile } from '../entities/user-profile.entity';
|
||||
import { PaymentsModule } from '../payments/payments.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Device, InventoryDevice])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Device, InventoryDevice, UserProfile]),
|
||||
forwardRef(() => PaymentsModule),
|
||||
],
|
||||
controllers: [DevicesController],
|
||||
providers: [DevicesService],
|
||||
exports: [DevicesService],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { EventsController } from './events.controller';
|
||||
import { EventsService } from './events.service';
|
||||
@ -6,9 +6,14 @@ import { AwsService } from '../aws/aws.service';
|
||||
import { RawEvent } from '../entities/raw-event.entity';
|
||||
import { Device } from '../entities/device.entity';
|
||||
import { ValidatedEvent } from '../entities/validated-event.entity';
|
||||
import { UserProfile } from '../entities/user-profile.entity';
|
||||
import { PaymentsModule } from '../payments/payments.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([RawEvent, Device, ValidatedEvent])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([RawEvent, Device, ValidatedEvent, UserProfile]),
|
||||
forwardRef(() => PaymentsModule),
|
||||
],
|
||||
controllers: [EventsController],
|
||||
providers: [EventsService, AwsService],
|
||||
exports: [EventsService, AwsService],
|
||||
|
||||
@ -24,27 +24,30 @@ async function bootstrap() {
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
// Configure raw body parsing for webhook endpoints
|
||||
app.use(
|
||||
'/api/v1/payments/webhook',
|
||||
json({
|
||||
verify: (req: any, res, buf) => {
|
||||
req.rawBody = buf;
|
||||
},
|
||||
}),
|
||||
);
|
||||
// fixme: 打开后json反序列化失败
|
||||
// // Configure raw body parsing for webhook endpoints
|
||||
// app.use(
|
||||
// '/api/v1/payments/webhook',
|
||||
// json({
|
||||
// verify: (req: any, res, buf) => {
|
||||
// req.rawBody = buf;
|
||||
// },
|
||||
// }),
|
||||
// );
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
skipMissingProperties: false,
|
||||
validationError: { target: false },
|
||||
}),
|
||||
);
|
||||
|
||||
// Enable CORS for frontend
|
||||
app.enableCors({
|
||||
origin: 'http://localhost:3001', // Adjust if your frontend runs on different port
|
||||
origin: 'http://localhost:3000', // Frontend runs on port 3000
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
|
||||
@ -182,6 +182,25 @@ export class PaymentsService {
|
||||
};
|
||||
}
|
||||
|
||||
// Development/Test mode: if subscription ID starts with 'sub_test_', return mock data
|
||||
if (userProfile.paymentProviderSubscriptionId.startsWith('sub_test_')) {
|
||||
this.logger.log(`Returning mock subscription data for development mode`);
|
||||
const nextMonth = new Date();
|
||||
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
||||
|
||||
return {
|
||||
hasActiveSubscription: true,
|
||||
subscriptionStatus: 'active',
|
||||
currentPlan: {
|
||||
id: userProfile.paymentProviderSubscriptionId,
|
||||
priceId: 'price_test_premium',
|
||||
name: 'Premium Plan (Test)',
|
||||
},
|
||||
nextBillingDate: nextMonth,
|
||||
customerId: userProfile.paymentProviderCustomerId,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const provider = this.getProvider('stripe');
|
||||
const subscription = await provider.getSubscription(
|
||||
|
||||
1494
package-lock.json
generated
1494
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -29,5 +29,8 @@
|
||||
"workspaces": [
|
||||
"meteor-web-backend",
|
||||
"meteor-frontend"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user