90dc-core 1.19.4 → 1.19.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/Errors/AppError.js +104 -0
- package/dist/lib/Errors/AppError.js.map +1 -0
- package/dist/lib/Errors/Errors.js +42 -0
- package/dist/lib/Errors/Errors.js.map +1 -0
- package/dist/lib/classes/Database.js +154 -0
- package/dist/lib/classes/Database.js.map +1 -0
- package/dist/lib/classes/Redis.js +63 -0
- package/dist/lib/classes/Redis.js.map +1 -0
- package/dist/lib/clients/EmailClient.js +188 -0
- package/dist/lib/clients/EmailClient.js.map +1 -0
- package/dist/lib/clients/FirebasePushNotificationClient.js +688 -0
- package/dist/lib/clients/FirebasePushNotificationClient.js.map +1 -0
- package/dist/lib/clients/ImagesClient.d.ts +2 -1
- package/dist/lib/clients/ImagesClient.d.ts.map +1 -1
- package/dist/lib/clients/ImagesClient.js +436 -0
- package/dist/lib/clients/ImagesClient.js.map +1 -0
- package/dist/lib/clients/MailingClient.js +84 -0
- package/dist/lib/clients/MailingClient.js.map +1 -0
- package/dist/lib/clients/PushNotificationClient.js +478 -0
- package/dist/lib/clients/PushNotificationClient.js.map +1 -0
- package/dist/lib/clients/types/email.types.js +3 -0
- package/dist/lib/clients/types/email.types.js.map +1 -0
- package/dist/lib/clients/types/images.types.js +3 -0
- package/dist/lib/clients/types/images.types.js.map +1 -0
- package/dist/lib/clients/types/mailing.types.js +3 -0
- package/dist/lib/clients/types/mailing.types.js.map +1 -0
- package/dist/lib/config/ConfigValidator.js +162 -0
- package/dist/lib/config/ConfigValidator.js.map +1 -0
- package/dist/lib/controllers/BaseController.js +64 -0
- package/dist/lib/controllers/BaseController.js.map +1 -0
- package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.js +60 -0
- package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.js.map +1 -0
- package/dist/lib/dbmodels/coaching/Answer.js +51 -0
- package/dist/lib/dbmodels/coaching/Answer.js.map +1 -0
- package/dist/lib/dbmodels/coaching/ClientNote.js +75 -0
- package/dist/lib/dbmodels/coaching/ClientNote.js.map +1 -0
- package/dist/lib/dbmodels/coaching/ClientTag.js +45 -0
- package/dist/lib/dbmodels/coaching/ClientTag.js.map +1 -0
- package/dist/lib/dbmodels/coaching/Question.js +60 -0
- package/dist/lib/dbmodels/coaching/Question.js.map +1 -0
- package/dist/lib/dbmodels/coaching/Questionnaire.js +39 -0
- package/dist/lib/dbmodels/coaching/Questionnaire.js.map +1 -0
- package/dist/lib/dbmodels/coaching/QuestionnaireResponse.js +42 -0
- package/dist/lib/dbmodels/coaching/QuestionnaireResponse.js.map +1 -0
- package/dist/lib/dbmodels/coaching/WeeklyCheckIn.js +69 -0
- package/dist/lib/dbmodels/coaching/WeeklyCheckIn.js.map +1 -0
- package/dist/lib/dbmodels/coaching/WeightRecord.js +49 -0
- package/dist/lib/dbmodels/coaching/WeightRecord.js.map +1 -0
- package/dist/lib/dbmodels/diet/DietDay.js +50 -0
- package/dist/lib/dbmodels/diet/DietDay.js.map +1 -0
- package/dist/lib/dbmodels/diet/DietMeal.js +74 -0
- package/dist/lib/dbmodels/diet/DietMeal.js.map +1 -0
- package/dist/lib/dbmodels/diet/DietMealCompletion.js +69 -0
- package/dist/lib/dbmodels/diet/DietMealCompletion.js.map +1 -0
- package/dist/lib/dbmodels/diet/DietMealRecipe.js +67 -0
- package/dist/lib/dbmodels/diet/DietMealRecipe.js.map +1 -0
- package/dist/lib/dbmodels/diet/DietMealRecipeIngredient.js +46 -0
- package/dist/lib/dbmodels/diet/DietMealRecipeIngredient.js.map +1 -0
- package/dist/lib/dbmodels/diet/DietProgram.js +98 -0
- package/dist/lib/dbmodels/diet/DietProgram.js.map +1 -0
- package/dist/lib/dbmodels/diet/Ingredient.js +88 -0
- package/dist/lib/dbmodels/diet/Ingredient.js.map +1 -0
- package/dist/lib/dbmodels/diet/IngredientTag.js +64 -0
- package/dist/lib/dbmodels/diet/IngredientTag.js.map +1 -0
- package/dist/lib/dbmodels/diet/IngredientTags.js +29 -0
- package/dist/lib/dbmodels/diet/IngredientTags.js.map +1 -0
- package/dist/lib/dbmodels/diet/Recipe.js +173 -0
- package/dist/lib/dbmodels/diet/Recipe.js.map +1 -0
- package/dist/lib/dbmodels/diet/RecipeIngredient.js +78 -0
- package/dist/lib/dbmodels/diet/RecipeIngredient.js.map +1 -0
- package/dist/lib/dbmodels/diet/RecipeTag.js +44 -0
- package/dist/lib/dbmodels/diet/RecipeTag.js.map +1 -0
- package/dist/lib/dbmodels/diet/RecipeTags.js +24 -0
- package/dist/lib/dbmodels/diet/RecipeTags.js.map +1 -0
- package/dist/lib/dbmodels/diet/ShoppingList.js +69 -0
- package/dist/lib/dbmodels/diet/ShoppingList.js.map +1 -0
- package/dist/lib/dbmodels/diet/ShoppingListItem.js +66 -0
- package/dist/lib/dbmodels/diet/ShoppingListItem.js.map +1 -0
- package/dist/lib/dbmodels/diet/TranslatedRecipe.js +122 -0
- package/dist/lib/dbmodels/diet/TranslatedRecipe.js.map +1 -0
- package/dist/lib/dbmodels/diet/TranslatedRecipeTag.js +47 -0
- package/dist/lib/dbmodels/diet/TranslatedRecipeTag.js.map +1 -0
- package/dist/lib/dbmodels/diet/TranslatedRecipeTags.js +24 -0
- package/dist/lib/dbmodels/diet/TranslatedRecipeTags.js.map +1 -0
- package/dist/lib/dbmodels/diet/UserDietPreferences.js +85 -0
- package/dist/lib/dbmodels/diet/UserDietPreferences.js.map +1 -0
- package/dist/lib/dbmodels/gamification/Badge.js +62 -0
- package/dist/lib/dbmodels/gamification/Badge.js.map +1 -0
- package/dist/lib/dbmodels/gamification/StreaksLog.js +37 -0
- package/dist/lib/dbmodels/gamification/StreaksLog.js.map +1 -0
- package/dist/lib/dbmodels/gamification/TranslatedBadge.js +45 -0
- package/dist/lib/dbmodels/gamification/TranslatedBadge.js.map +1 -0
- package/dist/lib/dbmodels/gamification/xpAndLeaderboards/UserRankHistory.js +48 -0
- package/dist/lib/dbmodels/gamification/xpAndLeaderboards/UserRankHistory.js.map +1 -0
- package/dist/lib/dbmodels/gamification/xpAndLeaderboards/XpEvent.js +53 -0
- package/dist/lib/dbmodels/gamification/xpAndLeaderboards/XpEvent.js.map +1 -0
- package/dist/lib/dbmodels/gamification/xpAndLeaderboards/XpTransaction.js +68 -0
- package/dist/lib/dbmodels/gamification/xpAndLeaderboards/XpTransaction.js.map +1 -0
- package/dist/lib/dbmodels/index.js +288 -0
- package/dist/lib/dbmodels/index.js.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgram.d.ts +32 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgram.d.ts.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgram.js +155 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgram.js.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgramWebContent.d.ts +22 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgramWebContent.d.ts.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgramWebContent.js +108 -0
- package/dist/lib/dbmodels/nonconsprogram/NonConsumableProgramWebContent.js.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedConsumableProgram.d.ts +20 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedConsumableProgram.d.ts.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedConsumableProgram.js +97 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedConsumableProgram.js.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedNonConsumableProgramWebContent.d.ts +22 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedNonConsumableProgramWebContent.d.ts.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedNonConsumableProgramWebContent.js +115 -0
- package/dist/lib/dbmodels/nonconsprogram/TranslatedNonConsumableProgramWebContent.js.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/UserNonConsumableProgram.d.ts +7 -0
- package/dist/lib/dbmodels/nonconsprogram/UserNonConsumableProgram.d.ts.map +1 -0
- package/dist/lib/dbmodels/nonconsprogram/UserNonConsumableProgram.js +41 -0
- package/dist/lib/dbmodels/nonconsprogram/UserNonConsumableProgram.js.map +1 -0
- package/dist/lib/dbmodels/notifications/NotificationModels.js +38 -0
- package/dist/lib/dbmodels/notifications/NotificationModels.js.map +1 -0
- package/dist/lib/dbmodels/notifications/TranslatedNotification.js +45 -0
- package/dist/lib/dbmodels/notifications/TranslatedNotification.js.map +1 -0
- package/dist/lib/dbmodels/program/Challenge.d.ts +13 -0
- package/dist/lib/dbmodels/program/Challenge.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/Challenge.js +63 -0
- package/dist/lib/dbmodels/program/Challenge.js.map +1 -0
- package/dist/lib/dbmodels/program/ChallengeBlueprint.js +75 -0
- package/dist/lib/dbmodels/program/ChallengeBlueprint.js.map +1 -0
- package/dist/lib/dbmodels/program/CircularProgramDraft.js +82 -0
- package/dist/lib/dbmodels/program/CircularProgramDraft.js.map +1 -0
- package/dist/lib/dbmodels/program/CoachExerciseNote.js +61 -0
- package/dist/lib/dbmodels/program/CoachExerciseNote.js.map +1 -0
- package/dist/lib/dbmodels/program/CustomProgramBlueprint.js +84 -0
- package/dist/lib/dbmodels/program/CustomProgramBlueprint.js.map +1 -0
- package/dist/lib/dbmodels/program/CustomStrengthTest.js +55 -0
- package/dist/lib/dbmodels/program/CustomStrengthTest.js.map +1 -0
- package/dist/lib/dbmodels/program/CustomStrengthTestExercises.js +69 -0
- package/dist/lib/dbmodels/program/CustomStrengthTestExercises.js.map +1 -0
- package/dist/lib/dbmodels/program/CustomWorkoutBlueprint.js +58 -0
- package/dist/lib/dbmodels/program/CustomWorkoutBlueprint.js.map +1 -0
- package/dist/lib/dbmodels/program/Exercise.d.ts +23 -0
- package/dist/lib/dbmodels/program/Exercise.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/Exercise.js +101 -0
- package/dist/lib/dbmodels/program/Exercise.js.map +1 -0
- package/dist/lib/dbmodels/program/ExerciseModels.d.ts +28 -0
- package/dist/lib/dbmodels/program/ExerciseModels.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/ExerciseModels.js +149 -0
- package/dist/lib/dbmodels/program/ExerciseModels.js.map +1 -0
- package/dist/lib/dbmodels/program/Program.d.ts +15 -0
- package/dist/lib/dbmodels/program/Program.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/Program.js +76 -0
- package/dist/lib/dbmodels/program/Program.js.map +1 -0
- package/dist/lib/dbmodels/program/ProgressEntry.d.ts +26 -0
- package/dist/lib/dbmodels/program/ProgressEntry.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/ProgressEntry.js +77 -0
- package/dist/lib/dbmodels/program/ProgressEntry.js.map +1 -0
- package/dist/lib/dbmodels/program/RestDay.js +33 -0
- package/dist/lib/dbmodels/program/RestDay.js.map +1 -0
- package/dist/lib/dbmodels/program/StrengthTest.d.ts +10 -0
- package/dist/lib/dbmodels/program/StrengthTest.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/StrengthTest.js +39 -0
- package/dist/lib/dbmodels/program/StrengthTest.js.map +1 -0
- package/dist/lib/dbmodels/program/StrengthTestExercise.d.ts +15 -0
- package/dist/lib/dbmodels/program/StrengthTestExercise.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/StrengthTestExercise.js +72 -0
- package/dist/lib/dbmodels/program/StrengthTestExercise.js.map +1 -0
- package/dist/lib/dbmodels/program/StrengthTestSession.js +73 -0
- package/dist/lib/dbmodels/program/StrengthTestSession.js.map +1 -0
- package/dist/lib/dbmodels/program/Superset.d.ts +13 -0
- package/dist/lib/dbmodels/program/Superset.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/Superset.js +46 -0
- package/dist/lib/dbmodels/program/Superset.js.map +1 -0
- package/dist/lib/dbmodels/program/ThirtyDayChallenge.js +70 -0
- package/dist/lib/dbmodels/program/ThirtyDayChallenge.js.map +1 -0
- package/dist/lib/dbmodels/program/ThirtyDayChallengeStrengthTest.js +55 -0
- package/dist/lib/dbmodels/program/ThirtyDayChallengeStrengthTest.js.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedChallenge.d.ts +15 -0
- package/dist/lib/dbmodels/program/TranslatedChallenge.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedChallenge.js +69 -0
- package/dist/lib/dbmodels/program/TranslatedChallenge.js.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedExerciseModel.d.ts +25 -0
- package/dist/lib/dbmodels/program/TranslatedExerciseModel.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedExerciseModel.js +123 -0
- package/dist/lib/dbmodels/program/TranslatedExerciseModel.js.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTest.d.ts +12 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTest.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTest.js +45 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTest.js.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTestExercise.d.ts +16 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTestExercise.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTestExercise.js +78 -0
- package/dist/lib/dbmodels/program/TranslatedStrengthTestExercise.js.map +1 -0
- package/dist/lib/dbmodels/program/UserChallenge.d.ts +10 -0
- package/dist/lib/dbmodels/program/UserChallenge.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/UserChallenge.js +47 -0
- package/dist/lib/dbmodels/program/UserChallenge.js.map +1 -0
- package/dist/lib/dbmodels/program/UserExerciseNote.js +61 -0
- package/dist/lib/dbmodels/program/UserExerciseNote.js.map +1 -0
- package/dist/lib/dbmodels/program/UserStrengthTests.d.ts +11 -0
- package/dist/lib/dbmodels/program/UserStrengthTests.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/UserStrengthTests.js +54 -0
- package/dist/lib/dbmodels/program/UserStrengthTests.js.map +1 -0
- package/dist/lib/dbmodels/program/Workout.d.ts +14 -0
- package/dist/lib/dbmodels/program/Workout.d.ts.map +1 -0
- package/dist/lib/dbmodels/program/Workout.js +66 -0
- package/dist/lib/dbmodels/program/Workout.js.map +1 -0
- package/dist/lib/dbmodels/program/WorkoutCompletion.js +65 -0
- package/dist/lib/dbmodels/program/WorkoutCompletion.js.map +1 -0
- package/dist/lib/dbmodels/program/WorkoutSession.js +93 -0
- package/dist/lib/dbmodels/program/WorkoutSession.js.map +1 -0
- package/dist/lib/dbmodels/subscription/Subscription.d.ts +19 -0
- package/dist/lib/dbmodels/subscription/Subscription.d.ts.map +1 -0
- package/dist/lib/dbmodels/subscription/Subscription.js +91 -0
- package/dist/lib/dbmodels/subscription/Subscription.js.map +1 -0
- package/dist/lib/dbmodels/subscription/SubscriptionEvent.js +79 -0
- package/dist/lib/dbmodels/subscription/SubscriptionEvent.js.map +1 -0
- package/dist/lib/dbmodels/subscription/SubscriptionLog.d.ts +9 -0
- package/dist/lib/dbmodels/subscription/SubscriptionLog.d.ts.map +1 -0
- package/dist/lib/dbmodels/subscription/SubscriptionLog.js +42 -0
- package/dist/lib/dbmodels/subscription/SubscriptionLog.js.map +1 -0
- package/dist/lib/dbmodels/subscription/SubscriptionRefund.js +92 -0
- package/dist/lib/dbmodels/subscription/SubscriptionRefund.js.map +1 -0
- package/dist/lib/dbmodels/user/DeviceTokens.js +34 -0
- package/dist/lib/dbmodels/user/DeviceTokens.js.map +1 -0
- package/dist/lib/dbmodels/user/PersistedUser.d.ts +43 -0
- package/dist/lib/dbmodels/user/PersistedUser.d.ts.map +1 -0
- package/dist/lib/dbmodels/user/PersistedUser.js +237 -0
- package/dist/lib/dbmodels/user/PersistedUser.js.map +1 -0
- package/dist/lib/dbmodels/user/UserAddons.js +26 -0
- package/dist/lib/dbmodels/user/UserAddons.js.map +1 -0
- package/dist/lib/dbmodels/user/UserBadges.js +63 -0
- package/dist/lib/dbmodels/user/UserBadges.js.map +1 -0
- package/dist/lib/dbmodels/user/UserCoach.js +40 -0
- package/dist/lib/dbmodels/user/UserCoach.js.map +1 -0
- package/dist/lib/dbmodels/user/UserDiscount.js +24 -0
- package/dist/lib/dbmodels/user/UserDiscount.js.map +1 -0
- package/dist/lib/dbmodels/user/UserOptions.js +34 -0
- package/dist/lib/dbmodels/user/UserOptions.js.map +1 -0
- package/dist/lib/dbmodels/user/UserProgressPhoto.js +59 -0
- package/dist/lib/dbmodels/user/UserProgressPhoto.js.map +1 -0
- package/dist/lib/dbmodels/user/UserStreaks.js +64 -0
- package/dist/lib/dbmodels/user/UserStreaks.js.map +1 -0
- package/dist/lib/dbmodels/user/UsersFriends.js +46 -0
- package/dist/lib/dbmodels/user/UsersFriends.js.map +1 -0
- package/dist/lib/enums/ProgramEnums.d.ts +18 -0
- package/dist/lib/enums/ProgramEnums.d.ts.map +1 -0
- package/dist/lib/enums/ProgramEnums.js +22 -0
- package/dist/lib/enums/ProgramEnums.js.map +1 -0
- package/dist/lib/enums/ProgramTemplates.js +738 -0
- package/dist/lib/enums/ProgramTemplates.js.map +1 -0
- package/dist/lib/middlewares/ErrorMiddleware.js +141 -0
- package/dist/lib/middlewares/ErrorMiddleware.js.map +1 -0
- package/dist/lib/middlewares/ValidationMiddleware.js +24 -0
- package/dist/lib/middlewares/ValidationMiddleware.js.map +1 -0
- package/dist/lib/models/BlueprintInterfaces.js +3 -0
- package/dist/lib/models/BlueprintInterfaces.js.map +1 -0
- package/dist/lib/models/ExerciseInterfaces.d.ts +68 -0
- package/dist/lib/models/ExerciseInterfaces.d.ts.map +1 -0
- package/dist/lib/models/ExerciseInterfaces.js +3 -0
- package/dist/lib/models/ExerciseInterfaces.js.map +1 -0
- package/dist/lib/models/NotificationInterfaces.js +11 -0
- package/dist/lib/models/NotificationInterfaces.js.map +1 -0
- package/dist/lib/models/ProgramInterfaces.js +3 -0
- package/dist/lib/models/ProgramInterfaces.js.map +1 -0
- package/dist/lib/models/UserInterfaces.js +3 -0
- package/dist/lib/models/UserInterfaces.js.map +1 -0
- package/dist/lib/models/WorkoutInterfaces.js +3 -0
- package/dist/lib/models/WorkoutInterfaces.js.map +1 -0
- package/dist/lib/scripts/cli.js +82 -0
- package/dist/lib/scripts/cli.js.map +1 -0
- package/dist/lib/scripts/populate-exercise-thumbnails.js +64 -0
- package/dist/lib/scripts/populate-exercise-thumbnails.js.map +1 -0
- package/dist/lib/scripts/setup-database.js +43 -0
- package/dist/lib/scripts/setup-database.js.map +1 -0
- package/dist/lib/scripts/verify-indexes.js +94 -0
- package/dist/lib/scripts/verify-indexes.js.map +1 -0
- package/dist/lib/testing/testFixtures.js +520 -0
- package/dist/lib/testing/testFixtures.js.map +1 -0
- package/dist/lib/testing/testHelpers.js +149 -0
- package/dist/lib/testing/testHelpers.js.map +1 -0
- package/dist/lib/utils/AuthenticationUtil.js +481 -0
- package/dist/lib/utils/AuthenticationUtil.js.map +1 -0
- package/dist/lib/utils/Logger.js +242 -0
- package/dist/lib/utils/Logger.js.map +1 -0
- package/dist/lib/utils/NotificationClient.js +371 -0
- package/dist/lib/utils/NotificationClient.js.map +1 -0
- package/dist/lib/utils/NotificationsUtil.js +95 -0
- package/dist/lib/utils/NotificationsUtil.js.map +1 -0
- package/dist/lib/utils/SecretManager.js +107 -0
- package/dist/lib/utils/SecretManager.js.map +1 -0
- package/dist/lib/utils/SentryUtil.js +140 -0
- package/dist/lib/utils/SentryUtil.js.map +1 -0
- package/dist/lib/utils/imageValidation.js +31 -0
- package/dist/lib/utils/imageValidation.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import * as dotenv from "dotenv";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import axios, { isAxiosError } from "axios";
|
|
4
|
+
import { google } from "googleapis";
|
|
5
|
+
import * as bcrypt from "bcryptjs";
|
|
6
|
+
import { OAuth2Client as OAuth2ClientImpl } from "google-auth-library";
|
|
7
|
+
import jwksClient from "jwks-rsa";
|
|
8
|
+
import { AppleTransactionError } from "../Errors/Errors.js";
|
|
9
|
+
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
|
|
10
|
+
import { Log } from "./Logger.js";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { AuthenticationError } from "../Errors/AppError.js";
|
|
13
|
+
import { PersistedUser } from "../dbmodels/user/PersistedUser.js";
|
|
14
|
+
dotenv.config();
|
|
15
|
+
const logger = Log.getInstance().extend("authentication-util");
|
|
16
|
+
var TokenExpiration = /*#__PURE__*/ function(TokenExpiration) {
|
|
17
|
+
TokenExpiration[TokenExpiration["ACCESS"] = 3600] = "ACCESS";
|
|
18
|
+
TokenExpiration[TokenExpiration["REFRESH"] = 2592000] = "REFRESH"; // 30 days in seconds
|
|
19
|
+
return TokenExpiration;
|
|
20
|
+
}(TokenExpiration || {});
|
|
21
|
+
const appleJwksClient = jwksClient({
|
|
22
|
+
jwksUri: "https://appleid.apple.com/auth/keys"
|
|
23
|
+
});
|
|
24
|
+
async function loadAndroidServiceAccount() {
|
|
25
|
+
const sm = new SecretManagerServiceClient();
|
|
26
|
+
const name = "projects/1033066542238/secrets/ANDROID_SUB_KEY/versions/latest";
|
|
27
|
+
const [v] = await sm.accessSecretVersion({
|
|
28
|
+
name
|
|
29
|
+
});
|
|
30
|
+
const raw = v.payload?.data?.toString("utf8") ?? "";
|
|
31
|
+
// Secret should be the full JSON key. Parse and normalize newlines.
|
|
32
|
+
const json = JSON.parse(raw);
|
|
33
|
+
const client_email = json.client_email;
|
|
34
|
+
const private_key = String(json.private_key).replace(/\\n/g, "\n");
|
|
35
|
+
return {
|
|
36
|
+
client_email,
|
|
37
|
+
private_key
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function loadAppleSubscriptionkey() {
|
|
41
|
+
const client = new SecretManagerServiceClient();
|
|
42
|
+
const name = "projects/1033066542238/secrets/APPLE_SUB_KEY/versions/latest";
|
|
43
|
+
const [v] = await client.accessSecretVersion({
|
|
44
|
+
name
|
|
45
|
+
});
|
|
46
|
+
return v.payload?.data?.toString("utf8") ?? "";
|
|
47
|
+
}
|
|
48
|
+
export class AuthenticationUtil {
|
|
49
|
+
static ACCESS_SECRET = process.env.ACCESS_TOKEN_SECRET;
|
|
50
|
+
static REFRESH_SECRET = process.env.REFRESH_TOKEN_SECRET;
|
|
51
|
+
static async fetchUserWithTokenInfo(token) {
|
|
52
|
+
const userInToken = await AuthenticationUtil.verifyTokenAndFetchUser(token);
|
|
53
|
+
if (!userInToken || !userInToken.userUuid) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return userInToken;
|
|
57
|
+
}
|
|
58
|
+
static verifyTokenAndFetchUser(token) {
|
|
59
|
+
return new Promise((resolve, reject)=>{
|
|
60
|
+
jwt.verify(token, this.ACCESS_SECRET, (err, decoded)=>{
|
|
61
|
+
if (err) {
|
|
62
|
+
reject(err);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (decoded === undefined) {
|
|
66
|
+
resolve(null);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const user = decoded;
|
|
70
|
+
if (!user.userUuid) {
|
|
71
|
+
resolve(false);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
PersistedUser.findByPk(user.userUuid).then((persistedUser)=>{
|
|
75
|
+
resolve(persistedUser);
|
|
76
|
+
}).catch((e)=>{
|
|
77
|
+
reject(e);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Sign access token
|
|
84
|
+
*/ static signAccessToken(user) {
|
|
85
|
+
return jwt.sign(user, this.ACCESS_SECRET, {
|
|
86
|
+
expiresIn: 3600
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sign refresh token
|
|
91
|
+
*/ static signRefreshToken(user) {
|
|
92
|
+
return jwt.sign(user, this.REFRESH_SECRET);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Verify refresh token and return new access token
|
|
96
|
+
*/ static verifyRefreshToken(refreshToken) {
|
|
97
|
+
return new Promise((resolve, reject)=>{
|
|
98
|
+
jwt.verify(refreshToken, this.REFRESH_SECRET, async (err, user)=>{
|
|
99
|
+
if (err) {
|
|
100
|
+
reject(err);
|
|
101
|
+
}
|
|
102
|
+
if (user === undefined) {
|
|
103
|
+
resolve(null);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const userTypes = user;
|
|
107
|
+
const newAccessToken = this.signAccessToken(userTypes);
|
|
108
|
+
resolve(newAccessToken);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// ==================== Password Management ====================
|
|
113
|
+
/**
|
|
114
|
+
* Hash password using bcrypt
|
|
115
|
+
*/ static async hashPassword(password) {
|
|
116
|
+
return bcrypt.hash(password, 10);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Compare password with hash
|
|
120
|
+
*/ static async comparePassword(plainPassword, hash) {
|
|
121
|
+
return bcrypt.compare(plainPassword, hash);
|
|
122
|
+
}
|
|
123
|
+
static async checkIfUserAlreadyRegistered(email) {
|
|
124
|
+
if (!email) return false;
|
|
125
|
+
const user = await PersistedUser.findOne({
|
|
126
|
+
where: {
|
|
127
|
+
email
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return user !== null;
|
|
131
|
+
}
|
|
132
|
+
static async login(email, password) {
|
|
133
|
+
const users = await PersistedUser.findAll({
|
|
134
|
+
where: {
|
|
135
|
+
email
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
if (!users || users.length === 0) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
for (const user of users){
|
|
142
|
+
const passwordsMatch = await this.comparePassword(password, user.password);
|
|
143
|
+
if (passwordsMatch) {
|
|
144
|
+
return user;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
static async verifyGoogleToken(idToken, audience) {
|
|
150
|
+
const client = new OAuth2ClientImpl(audience);
|
|
151
|
+
try {
|
|
152
|
+
return await client.verifyIdToken({
|
|
153
|
+
idToken,
|
|
154
|
+
audience
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error("Google Token Verification Error:", error);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
static getGoogleClientId(platform) {
|
|
162
|
+
switch(platform){
|
|
163
|
+
case "android":
|
|
164
|
+
return process.env.ANDROID_GOOGLE_CLIENT_ID;
|
|
165
|
+
case "ios":
|
|
166
|
+
return process.env.IOS_GOOGLE_CLIENT_ID;
|
|
167
|
+
case "web":
|
|
168
|
+
return process.env.WEB_GOOGLE_CLIENT_ID;
|
|
169
|
+
default:
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
static async generateAppleJWT() {
|
|
174
|
+
const privateKey = await loadAppleSubscriptionkey();
|
|
175
|
+
const header = {
|
|
176
|
+
alg: "ES256",
|
|
177
|
+
kid: process.env.APPLE_KID,
|
|
178
|
+
typ: "JWT"
|
|
179
|
+
};
|
|
180
|
+
const payload = {
|
|
181
|
+
iss: process.env.APPLE_ISSUER,
|
|
182
|
+
iat: Math.floor(Date.now() / 1000),
|
|
183
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
184
|
+
aud: "appstoreconnect-v1",
|
|
185
|
+
bid: process.env.APPLE_BUNDLE_ID
|
|
186
|
+
};
|
|
187
|
+
return jwt.sign(payload, privateKey, {
|
|
188
|
+
header
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
static verifyAppleJwt(userData, key) {
|
|
192
|
+
try {
|
|
193
|
+
return jwt.verify(userData, key);
|
|
194
|
+
} catch (e) {
|
|
195
|
+
logger.error(e);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
static async getAppleSignInKey(kid) {
|
|
200
|
+
try {
|
|
201
|
+
const key = await appleJwksClient.getSigningKey(kid);
|
|
202
|
+
return key.getPublicKey();
|
|
203
|
+
} catch (e) {
|
|
204
|
+
logger.error(e);
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
static async verifyAppleReceipt(data) {
|
|
209
|
+
const response = await axios.post("https://buy.itunes.apple.com/verifyReceipt", {
|
|
210
|
+
"receipt-data": data,
|
|
211
|
+
password: process.env.APPLE_SHARED_SECRET,
|
|
212
|
+
"exclude-old-transactions": true
|
|
213
|
+
});
|
|
214
|
+
return response.data;
|
|
215
|
+
}
|
|
216
|
+
static async getAppleTransactions(originalTransactionId) {
|
|
217
|
+
const result = [];
|
|
218
|
+
try {
|
|
219
|
+
const response = await axios.get(`https://api.storekit.itunes.apple.com/inApps/v1/history/${originalTransactionId}`, {
|
|
220
|
+
headers: {
|
|
221
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
222
|
+
"Content-Type": "application/json"
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
for (const token of response.data.signedTransactions){
|
|
226
|
+
result.push(jwt.decode(token));
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (isAxiosError(error) && error.response?.status === 400) {
|
|
231
|
+
throw new AppleTransactionError("Invalid transaction id.");
|
|
232
|
+
}
|
|
233
|
+
if (isAxiosError(error) && (error.response?.data.errorCode === 4040010 || error.response?.status === 401)) {
|
|
234
|
+
try {
|
|
235
|
+
const response = await axios.get(`https://api.storekit-sandbox.itunes.apple.com/inApps/v1/history/${originalTransactionId}`, {
|
|
236
|
+
headers: {
|
|
237
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
238
|
+
"Content-Type": "application/json"
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
for (const token of response.data.signedTransactions){
|
|
242
|
+
result.push(jwt.decode(token));
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
} catch (e) {
|
|
246
|
+
if (isAxiosError(error) && error.response?.status === 400) {
|
|
247
|
+
throw new AppleTransactionError("Invalid transaction id.");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
throw new AppleTransactionError("Transaction was not found in both environments.");
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
static async getAppleSubscriptionsStatuses(transactionId) {
|
|
256
|
+
const result = [];
|
|
257
|
+
try {
|
|
258
|
+
const response = await axios.get(`https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/${transactionId}`, {
|
|
259
|
+
headers: {
|
|
260
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
261
|
+
"Content-Type": "application/json"
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
for (const transaction of response.data.data[0].lastTransactions){
|
|
265
|
+
result.push({
|
|
266
|
+
originalTransactionId: transaction.originalTransactionId,
|
|
267
|
+
status: transaction.status,
|
|
268
|
+
signedTransactionInfo: jwt.decode(transaction.signedTransactionInfo),
|
|
269
|
+
signedRenewalInfo: jwt.decode(transaction.signedRenewalInfo)
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (isAxiosError(error) && error.response?.status === 400) {
|
|
275
|
+
throw new AppleTransactionError("Invalid transaction id.");
|
|
276
|
+
}
|
|
277
|
+
// Try sandbox
|
|
278
|
+
if (isAxiosError(error) && (error.response?.data.errorCode === 4040010 || error.response?.status === 401)) {
|
|
279
|
+
try {
|
|
280
|
+
const response = await axios.get(`https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/${transactionId}`, {
|
|
281
|
+
headers: {
|
|
282
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
283
|
+
"Content-Type": "application/json"
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
for (const transaction of response.data.data[0].lastTransactions){
|
|
287
|
+
result.push({
|
|
288
|
+
originalTransactionId: transaction.originalTransactionId,
|
|
289
|
+
status: transaction.status,
|
|
290
|
+
signedTransactionInfo: jwt.decode(transaction.signedTransactionInfo),
|
|
291
|
+
signedRenewalInfo: jwt.decode(transaction.signedRenewalInfo)
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
} catch (e) {
|
|
296
|
+
if (isAxiosError(error) && error.response?.status === 400) {
|
|
297
|
+
throw new AppleTransactionError("Invalid transaction id.");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
throw new AppleTransactionError("Transaction was not found in both environments.");
|
|
301
|
+
}
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
static async isAppleSubscriptionActive(transactionId) {
|
|
306
|
+
try {
|
|
307
|
+
const response = await axios.get(`https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/${transactionId}`, {
|
|
308
|
+
headers: {
|
|
309
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
310
|
+
"Content-Type": "application/json"
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return response.data.data[0].lastTransactions[0].status === 0 || response.data.data[0].lastTransactions[0].status === 2;
|
|
314
|
+
} catch (e) {
|
|
315
|
+
if (axios.isAxiosError(e) && e.response && e.response.data["errorMessage"] === "Invalid transaction id.") {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
static async getSingleAppleTransactionsInfo(transactionId) {
|
|
322
|
+
try {
|
|
323
|
+
const response = await axios.get(`https://api.storekit.itunes.apple.com/inApps/v1/transactions/${transactionId}`, {
|
|
324
|
+
headers: {
|
|
325
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
326
|
+
"Content-Type": "application/json"
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
signedTransactionInfo: jwt.decode(response.data.signedTransactionInfo)
|
|
331
|
+
};
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (isAxiosError(error) && error.response?.status === 400) {
|
|
334
|
+
throw new AppleTransactionError("Invalid transaction id.");
|
|
335
|
+
}
|
|
336
|
+
if (isAxiosError(error) && (error.response?.data.errorCode === 4040010 || error.response?.status === 401)) {
|
|
337
|
+
try {
|
|
338
|
+
const response = await axios.get(`https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/${transactionId}`, {
|
|
339
|
+
headers: {
|
|
340
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
341
|
+
"Content-Type": "application/json"
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
return {
|
|
345
|
+
signedTransactionInfo: jwt.decode(response.data.signedTransactionInfo)
|
|
346
|
+
};
|
|
347
|
+
} catch (e) {
|
|
348
|
+
if (isAxiosError(error) && error.response?.status === 400) {
|
|
349
|
+
throw new AppleTransactionError("Invalid transaction id.");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
throw new AppleTransactionError("Transaction was not found in both environments.");
|
|
353
|
+
}
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
static async checkIfUserBoughtProgram(transactionId) {
|
|
358
|
+
try {
|
|
359
|
+
const productIds = [];
|
|
360
|
+
const url = process.env.PURCHASE_HISTORY_URL || "https://api.storekit.itunes.apple.com";
|
|
361
|
+
const response = await axios.get(`${url}/inApps/v2/history/${transactionId}?productType=NON_CONSUMABLE`, {
|
|
362
|
+
headers: {
|
|
363
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`,
|
|
364
|
+
"Content-Type": "application/json"
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
for (const transaction of response.data.signedTransactions){
|
|
368
|
+
productIds.push(jwt.decode(transaction).productId);
|
|
369
|
+
}
|
|
370
|
+
return productIds;
|
|
371
|
+
} catch (e) {
|
|
372
|
+
logger.error(e.message);
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
static getUserFromContext(ctx) {
|
|
377
|
+
const user = ctx.state.user;
|
|
378
|
+
if (!user) {
|
|
379
|
+
ctx.throw(AuthenticationError);
|
|
380
|
+
}
|
|
381
|
+
return user;
|
|
382
|
+
}
|
|
383
|
+
static async extendAppleSubscription(transactionId, extendByDays) {
|
|
384
|
+
const response = await axios.put(`https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/extend/${transactionId}`, {
|
|
385
|
+
extendByDays,
|
|
386
|
+
extendReasonCode: 1,
|
|
387
|
+
requestIdentifier: randomUUID()
|
|
388
|
+
}, {
|
|
389
|
+
headers: {
|
|
390
|
+
Authorization: `Bearer ${await this.generateAppleJWT()}`
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
return response.data;
|
|
394
|
+
}
|
|
395
|
+
static async getAndroidAccessToken() {
|
|
396
|
+
const { client_email, private_key } = await loadAndroidServiceAccount();
|
|
397
|
+
const jwtClient = new google.auth.JWT({
|
|
398
|
+
email: client_email,
|
|
399
|
+
key: private_key,
|
|
400
|
+
scopes: [
|
|
401
|
+
"https://www.googleapis.com/auth/androidpublisher"
|
|
402
|
+
]
|
|
403
|
+
});
|
|
404
|
+
return await jwtClient.authorize();
|
|
405
|
+
}
|
|
406
|
+
static async getAndroidSubscriptionsStatuses(token) {
|
|
407
|
+
try {
|
|
408
|
+
const credentials = await this.getAndroidAccessToken();
|
|
409
|
+
const response = await axios.get(`https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${process.env.ANDROID_PACKAGE_NAME || "nl.browney.nintydayschallenge"}/purchases/subscriptionsv2/tokens/${token}`, {
|
|
410
|
+
headers: {
|
|
411
|
+
Authorization: `Bearer ${credentials.access_token}`
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
return response.data;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (isAxiosError(error) && error.response && error.response.status === 410) {
|
|
417
|
+
// Subscription expired
|
|
418
|
+
return {
|
|
419
|
+
lineItems: [
|
|
420
|
+
{
|
|
421
|
+
expiryTime: new Date(Date.now() - 86400000).toISOString()
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
};
|
|
425
|
+
} else {
|
|
426
|
+
if (isAxiosError(error)) {
|
|
427
|
+
logger.error("Android subscription error:", error.message);
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
static async isAndroidSubscriptionsActive(token) {
|
|
434
|
+
try {
|
|
435
|
+
const credentials = await this.getAndroidAccessToken();
|
|
436
|
+
const response = await axios.get(`https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${process.env.ANDROID_PACKAGE_NAME || "nl.browney.nintydayschallenge"}/purchases/subscriptionsv2/tokens/${token}`, {
|
|
437
|
+
headers: {
|
|
438
|
+
Authorization: `Bearer ${credentials.access_token}`
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
return response.data.subscriptionState === "SUBSCRIPTION_STATE_ACTIVE";
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (isAxiosError(error)) {
|
|
444
|
+
logger.error("An error occurred:", error.message);
|
|
445
|
+
}
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
static async extendAndroidSubscription(purchaseToken, subscriptionId, extendByDays) {
|
|
450
|
+
const credentials = await this.getAndroidAccessToken();
|
|
451
|
+
const packageName = process.env.ANDROID_PACKAGE_NAME || "nl.browney.nintydayschallenge";
|
|
452
|
+
// First, get the current subscription details to get the current expiry time
|
|
453
|
+
const currentSubResponse = await axios.get(`https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}/purchases/subscriptionsv2/tokens/${purchaseToken}`, {
|
|
454
|
+
headers: {
|
|
455
|
+
Authorization: `Bearer ${credentials.access_token}`
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
const currentExpiryTime = currentSubResponse.data.lineItems?.[0]?.expiryTime;
|
|
459
|
+
if (!currentExpiryTime) {
|
|
460
|
+
throw new Error("Unable to get current subscription expiry time");
|
|
461
|
+
}
|
|
462
|
+
// Calculate the new expiry time
|
|
463
|
+
const currentExpiryMs = new Date(currentExpiryTime).getTime();
|
|
464
|
+
const desiredExpiryMs = currentExpiryMs + extendByDays * 24 * 60 * 60 * 1000;
|
|
465
|
+
// Use the defer API to extend the subscription
|
|
466
|
+
const response = await axios.post(`https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${purchaseToken}:defer`, {
|
|
467
|
+
deferralInfo: {
|
|
468
|
+
expectedExpiryTimeMillis: currentExpiryMs.toString(),
|
|
469
|
+
desiredExpiryTimeMillis: desiredExpiryMs.toString()
|
|
470
|
+
}
|
|
471
|
+
}, {
|
|
472
|
+
headers: {
|
|
473
|
+
Authorization: `Bearer ${credentials.access_token}`,
|
|
474
|
+
"Content-Type": "application/json"
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
return response.data;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
//# sourceMappingURL=AuthenticationUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/utils/AuthenticationUtil.ts"],"sourcesContent":["import * as dotenv from \"dotenv\";\nimport jwt, {type JwtPayload, type VerifyErrors} from \"jsonwebtoken\";\nimport axios, {type AxiosResponse, isAxiosError} from \"axios\";\nimport {google} from \"googleapis\";\nimport * as bcrypt from \"bcryptjs\";\nimport type {LoginTicket,} from \"google-auth-library\";\nimport {OAuth2Client as OAuth2ClientImpl} from \"google-auth-library\";\nimport jwksClient, {type SigningKey} from \"jwks-rsa\";\nimport type {\n Credentials as CredentialsType,\n SignedTransactions,\n SingleTransactionResponse,\n SubscriptionStatusResponse,\n SubscriptionStatusResponseError,\n SubscriptionStatusResult,\n TransactionsResponse,\n UserTypes,\n VerificationStatus,\n} from '../models/UserInterfaces.js';\nimport {AppleTransactionError} from '../Errors/Errors.js';\nimport {SecretManagerServiceClient} from \"@google-cloud/secret-manager\";\nimport {Log} from \"./Logger.js\";\nimport {randomUUID} from \"node:crypto\";\nimport type {Context} from \"koa\";\nimport {AuthenticationError} from \"../Errors/AppError.js\";\nimport { PersistedUser } from \"lib/dbmodels/user/PersistedUser.js\";\n\ndotenv.config();\n\nconst logger: Log = Log.getInstance().extend(\"authentication-util\");\n\nenum TokenExpiration {\n ACCESS = 60 * 60, // 1 hour in seconds\n REFRESH = 30 * 24 * 60 * 60 // 30 days in seconds\n}\n\nconst appleJwksClient = jwksClient({\n jwksUri: \"https://appleid.apple.com/auth/keys\",\n});\n\nasync function loadAndroidServiceAccount(): Promise<{client_email: string; private_key: string}> {\n const sm = new SecretManagerServiceClient();\n const name = \"projects/1033066542238/secrets/ANDROID_SUB_KEY/versions/latest\";\n const [v] = await sm.accessSecretVersion({ name });\n const raw = v.payload?.data?.toString(\"utf8\") ?? \"\";\n\n // Secret should be the full JSON key. Parse and normalize newlines.\n const json = JSON.parse(raw);\n const client_email = json.client_email as string;\n const private_key = String(json.private_key).replace(/\\\\n/g, \"\\n\");\n return { client_email, private_key };\n}\n\nasync function loadAppleSubscriptionkey(): Promise<string> {\n const client = new SecretManagerServiceClient();\n const name = \"projects/1033066542238/secrets/APPLE_SUB_KEY/versions/latest\";\n const [v] = await client.accessSecretVersion({ name });\n return v.payload?.data?.toString(\"utf8\") ?? \"\";\n}\n\nexport class AuthenticationUtil {\n public static readonly ACCESS_SECRET = process.env.ACCESS_TOKEN_SECRET as string;\n public static readonly REFRESH_SECRET = process.env.REFRESH_TOKEN_SECRET as string;\n\n\n public static async fetchUserWithTokenInfo(token: string): Promise<PersistedUser | null> {\n const userInToken = await AuthenticationUtil.verifyTokenAndFetchUser(token);\n if (!userInToken || !userInToken.userUuid) {\n return null;\n }\n return userInToken;\n }\n\n public static verifyTokenAndFetchUser(token: string): Promise<PersistedUser | null | false> {\n return new Promise<PersistedUser | null | false>((resolve, reject) => {\n jwt.verify(\n token,\n this.ACCESS_SECRET,\n (err: VerifyErrors | null, decoded: JwtPayload | string | undefined) => {\n if (err) {\n reject(err);\n return;\n }\n\n if (decoded === undefined) {\n resolve(null);\n return;\n }\n\n const user: UserTypes = <UserTypes>decoded;\n\n if (!user.userUuid) {\n resolve(false);\n return;\n }\n\n PersistedUser.findByPk(user.userUuid)\n .then((persistedUser: PersistedUser | null) => {\n resolve(persistedUser);\n })\n .catch((e: Error) => {\n reject(e);\n });\n }\n );\n });\n }\n\n /**\n * Sign access token\n */\n public static signAccessToken(user: UserTypes): string {\n return jwt.sign(user, this.ACCESS_SECRET, {\n expiresIn: TokenExpiration.ACCESS,\n });\n }\n\n /**\n * Sign refresh token\n */\n public static signRefreshToken(user: UserTypes): string {\n return jwt.sign(user, this.REFRESH_SECRET);\n }\n\n /**\n * Verify refresh token and return new access token\n */\n public static verifyRefreshToken(refreshToken: string) {\n return new Promise<string | null | false>((resolve, reject) => {\n jwt.verify(\n refreshToken,\n this.REFRESH_SECRET,\n async (\n err: VerifyErrors | null,\n user: JwtPayload | string | undefined\n ) => {\n if (err) {\n reject(err);\n }\n\n if (user === undefined) {\n resolve(null);\n return;\n }\n const userTypes = <UserTypes>user;\n const newAccessToken = this.signAccessToken(userTypes);\n\n resolve(newAccessToken);\n }\n );\n });\n }\n\n // ==================== Password Management ====================\n\n /**\n * Hash password using bcrypt\n */\n public static async hashPassword(password: string): Promise<string> {\n return bcrypt.hash(password, 10);\n }\n\n /**\n * Compare password with hash\n */\n public static async comparePassword(plainPassword: string, hash: string): Promise<boolean> {\n return bcrypt.compare(plainPassword, hash);\n }\n\n public static async checkIfUserAlreadyRegistered(email: string | undefined): Promise<boolean> {\n if (!email) return false;\n const user = await PersistedUser.findOne({ where: { email } });\n return user !== null;\n }\n\n public static async login(email: string, password: string): Promise<PersistedUser | null> {\n const users = await PersistedUser.findAll({ where: { email } });\n\n if (!users || users.length === 0) {\n return null;\n }\n\n for (const user of users) {\n const passwordsMatch = await this.comparePassword(password, user.password);\n if (passwordsMatch) {\n return user;\n }\n }\n\n return null;\n }\n\n public static async verifyGoogleToken(\n idToken: string,\n audience: string\n ): Promise<LoginTicket | null> {\n const client = new OAuth2ClientImpl(audience);\n try {\n return await client.verifyIdToken({ idToken, audience });\n } catch (error) {\n logger.error(\"Google Token Verification Error:\", error);\n return null;\n }\n }\n\n public static getGoogleClientId(platform: string): string | undefined {\n switch (platform) {\n case \"android\":\n return process.env.ANDROID_GOOGLE_CLIENT_ID;\n case \"ios\":\n return process.env.IOS_GOOGLE_CLIENT_ID;\n case \"web\":\n return process.env.WEB_GOOGLE_CLIENT_ID;\n default:\n return undefined;\n }\n }\n\n\n public static async generateAppleJWT(): Promise<string> {\n const privateKey: string = await loadAppleSubscriptionkey()\n\n const header = {\n alg: \"ES256\",\n kid: process.env.APPLE_KID as string,\n typ: \"JWT\",\n };\n\n const payload = {\n iss: process.env.APPLE_ISSUER as string,\n iat: Math.floor(Date.now() / 1000),\n exp: Math.floor(Date.now() / 1000) + 3600,\n aud: \"appstoreconnect-v1\",\n bid: process.env.APPLE_BUNDLE_ID as string,\n };\n\n return jwt.sign(payload, privateKey, { header });\n }\n\n\n public static verifyAppleJwt(userData: string, key: string) {\n try {\n return jwt.verify(userData, key);\n } catch (e) {\n logger.error(e);\n return null;\n }\n }\n\n\n public static async getAppleSignInKey(kid: string): Promise<string | undefined> {\n try {\n const key: SigningKey = await appleJwksClient.getSigningKey(kid);\n return key.getPublicKey();\n } catch (e) {\n logger.error(e);\n return undefined;\n }\n }\n\n\n public static async verifyAppleReceipt(data: string): Promise<VerificationStatus> {\n const response: AxiosResponse = await axios.post(\n \"https://buy.itunes.apple.com/verifyReceipt\",\n {\n \"receipt-data\": data,\n password: process.env.APPLE_SHARED_SECRET as string,\n \"exclude-old-transactions\": true,\n }\n );\n\n return response.data as VerificationStatus;\n }\n\n public static async getAppleTransactions(\n originalTransactionId: string\n ): Promise<SignedTransactions[]> {\n const result: SignedTransactions[] = [];\n\n try {\n const response: AxiosResponse<TransactionsResponse> = await axios.get(\n `https://api.storekit.itunes.apple.com/inApps/v1/history/${originalTransactionId}`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n for (const token of response.data.signedTransactions) {\n result.push(jwt.decode(token) as SignedTransactions);\n }\n\n return result;\n } catch (error) {\n if (isAxiosError(error) && error.response?.status === 400) {\n throw new AppleTransactionError(\"Invalid transaction id.\");\n }\n\n if (\n isAxiosError(error) &&\n (error.response?.data.errorCode === 4040010 || error.response?.status === 401)\n ) {\n try {\n const response: AxiosResponse<TransactionsResponse> = await axios.get(\n `https://api.storekit-sandbox.itunes.apple.com/inApps/v1/history/${originalTransactionId}`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n for (const token of response.data.signedTransactions) {\n result.push(jwt.decode(token) as SignedTransactions);\n }\n\n return result;\n } catch (e) {\n if (isAxiosError(error) && error.response?.status === 400) {\n throw new AppleTransactionError(\"Invalid transaction id.\");\n }\n }\n throw new AppleTransactionError(\"Transaction was not found in both environments.\");\n }\n throw error;\n }\n }\n\n public static async getAppleSubscriptionsStatuses(\n transactionId: string\n ): Promise<SubscriptionStatusResult[]> {\n const result: SubscriptionStatusResult[] = [];\n\n try {\n const response: AxiosResponse<SubscriptionStatusResponse> = await axios.get(\n `https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/${transactionId}`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n for (const transaction of response.data.data[0].lastTransactions) {\n result.push({\n originalTransactionId: transaction.originalTransactionId,\n status: transaction.status,\n signedTransactionInfo: jwt.decode(transaction.signedTransactionInfo),\n signedRenewalInfo: jwt.decode(transaction.signedRenewalInfo),\n });\n }\n\n return result;\n } catch (error: unknown) {\n if (isAxiosError(error) && error.response?.status === 400) {\n throw new AppleTransactionError(\"Invalid transaction id.\");\n }\n\n // Try sandbox\n if (\n isAxiosError(error) &&\n (error.response?.data.errorCode === 4040010 || error.response?.status === 401)\n ) {\n try {\n const response: AxiosResponse<SubscriptionStatusResponse> = await axios.get(\n `https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/${transactionId}`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n for (const transaction of response.data.data[0].lastTransactions) {\n result.push({\n originalTransactionId: transaction.originalTransactionId,\n status: transaction.status,\n signedTransactionInfo: jwt.decode(transaction.signedTransactionInfo),\n signedRenewalInfo: jwt.decode(transaction.signedRenewalInfo),\n });\n }\n\n return result;\n } catch (e) {\n if (isAxiosError(error) && error.response?.status === 400) {\n throw new AppleTransactionError(\"Invalid transaction id.\");\n }\n }\n throw new AppleTransactionError(\"Transaction was not found in both environments.\");\n }\n throw error;\n }\n }\n\n public static async isAppleSubscriptionActive(transactionId: string): Promise<boolean> {\n try {\n const response: AxiosResponse<SubscriptionStatusResponse> = await axios.get(\n `https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/${transactionId}`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n return (\n response.data.data[0].lastTransactions[0].status === 0 ||\n response.data.data[0].lastTransactions[0].status === 2\n );\n } catch (e) {\n if (\n axios.isAxiosError(e) &&\n e.response &&\n (e.response.data as SubscriptionStatusResponseError)[\"errorMessage\"] ===\n \"Invalid transaction id.\"\n ) {\n return false;\n }\n return false;\n }\n }\n\n public static async getSingleAppleTransactionsInfo(transactionId: string) {\n try {\n const response: AxiosResponse<SingleTransactionResponse> = await axios.get(\n `https://api.storekit.itunes.apple.com/inApps/v1/transactions/${transactionId}`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n return { signedTransactionInfo: jwt.decode(response.data.signedTransactionInfo) };\n } catch (error) {\n if (isAxiosError(error) && error.response?.status === 400) {\n throw new AppleTransactionError(\"Invalid transaction id.\");\n }\n\n if (\n isAxiosError(error) &&\n (error.response?.data.errorCode === 4040010 || error.response?.status === 401)\n ) {\n try {\n const response: AxiosResponse<SingleTransactionResponse> = await axios.get(\n `https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/${transactionId}`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n return { signedTransactionInfo: jwt.decode(response.data.signedTransactionInfo) };\n } catch (e) {\n if (isAxiosError(error) && error.response?.status === 400) {\n throw new AppleTransactionError(\"Invalid transaction id.\");\n }\n }\n throw new AppleTransactionError(\"Transaction was not found in both environments.\");\n }\n throw error;\n }\n }\n\n\n public static async checkIfUserBoughtProgram(transactionId: string): Promise<string[]> {\n try {\n const productIds: string[] = [];\n const url = process.env.PURCHASE_HISTORY_URL || \"https://api.storekit.itunes.apple.com\";\n const response: AxiosResponse = await axios.get(\n `${url}/inApps/v2/history/${transactionId}?productType=NON_CONSUMABLE`,\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n for (const transaction of response.data.signedTransactions) {\n productIds.push((jwt.decode(transaction) as { productId: string }).productId);\n }\n\n return productIds;\n } catch (e) {\n logger.error((e as Error).message);\n return [];\n }\n }\n\n public static getUserFromContext(ctx: Context): PersistedUser {\n const user = ctx.state.user as PersistedUser;\n if (!user) {\n ctx.throw(AuthenticationError);\n }\n return user;\n }\n\n public static async extendAppleSubscription(\n transactionId: string,\n extendByDays: number\n ): Promise<any> {\n const response: AxiosResponse = await axios.put(\n `https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/extend/${transactionId}`,\n {\n extendByDays,\n extendReasonCode: 1,\n requestIdentifier: randomUUID(),\n },\n {\n headers: {\n Authorization: `Bearer ${await this.generateAppleJWT()}`,\n },\n }\n );\n\n return response.data;\n }\n\n public static async getAndroidAccessToken(): Promise<CredentialsType> {\n const { client_email, private_key } = await loadAndroidServiceAccount();\n\n const jwtClient = new google.auth.JWT({\n email: client_email,\n key: private_key, // unesc\n scopes: [\"https://www.googleapis.com/auth/androidpublisher\"],\n });\n\n return await jwtClient.authorize();\n }\n\n public static async getAndroidSubscriptionsStatuses(token: string) {\n try {\n const credentials = await this.getAndroidAccessToken();\n\n const response: AxiosResponse = await axios.get(\n `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${process.env.ANDROID_PACKAGE_NAME || \"nl.browney.nintydayschallenge\"}/purchases/subscriptionsv2/tokens/${token}`,\n {\n headers: {\n Authorization: `Bearer ${credentials.access_token as string}`,\n },\n }\n );\n\n return response.data;\n } catch (error) {\n if (isAxiosError(error) && error.response && error.response.status === 410) {\n // Subscription expired\n return {\n lineItems: [\n {\n expiryTime: new Date(Date.now() - 86400000).toISOString(), // Yesterday\n },\n ],\n };\n } else {\n if (isAxiosError(error)) {\n logger.error(\"Android subscription error:\", error.message);\n return false;\n }\n }\n }\n }\n\n public static async isAndroidSubscriptionsActive(token: string): Promise<boolean> {\n try {\n const credentials = await this.getAndroidAccessToken();\n\n const response: AxiosResponse = await axios.get(\n `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${process.env.ANDROID_PACKAGE_NAME || \"nl.browney.nintydayschallenge\"}/purchases/subscriptionsv2/tokens/${token}`,\n {\n headers: {\n Authorization: `Bearer ${credentials.access_token as string}`,\n },\n }\n );\n\n return (\n (response.data as { subscriptionState: string }).subscriptionState ===\n \"SUBSCRIPTION_STATE_ACTIVE\"\n );\n } catch (error) {\n if (isAxiosError(error)) {\n logger.error(\"An error occurred:\", error.message);\n }\n return false;\n }\n }\n\n public static async extendAndroidSubscription(\n purchaseToken: string,\n subscriptionId: string,\n extendByDays: number\n ): Promise<any> {\n const credentials = await this.getAndroidAccessToken();\n const packageName = process.env.ANDROID_PACKAGE_NAME || \"nl.browney.nintydayschallenge\";\n\n // First, get the current subscription details to get the current expiry time\n const currentSubResponse: AxiosResponse = await axios.get(\n `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}/purchases/subscriptionsv2/tokens/${purchaseToken}`,\n {\n headers: {\n Authorization: `Bearer ${credentials.access_token as string}`,\n },\n }\n );\n\n const currentExpiryTime = currentSubResponse.data.lineItems?.[0]?.expiryTime;\n if (!currentExpiryTime) {\n throw new Error(\"Unable to get current subscription expiry time\");\n }\n\n // Calculate the new expiry time\n const currentExpiryMs = new Date(currentExpiryTime).getTime();\n const desiredExpiryMs = currentExpiryMs + (extendByDays * 24 * 60 * 60 * 1000);\n\n // Use the defer API to extend the subscription\n const response: AxiosResponse = await axios.post(\n `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${purchaseToken}:defer`,\n {\n deferralInfo: {\n expectedExpiryTimeMillis: currentExpiryMs.toString(),\n desiredExpiryTimeMillis: desiredExpiryMs.toString(),\n },\n },\n {\n headers: {\n Authorization: `Bearer ${credentials.access_token as string}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n return response.data;\n }\n}\n"],"names":["dotenv","jwt","axios","isAxiosError","google","bcrypt","OAuth2Client","OAuth2ClientImpl","jwksClient","AppleTransactionError","SecretManagerServiceClient","Log","randomUUID","AuthenticationError","PersistedUser","config","logger","getInstance","extend","TokenExpiration","appleJwksClient","jwksUri","loadAndroidServiceAccount","sm","name","v","accessSecretVersion","raw","payload","data","toString","json","JSON","parse","client_email","private_key","String","replace","loadAppleSubscriptionkey","client","AuthenticationUtil","ACCESS_SECRET","process","env","ACCESS_TOKEN_SECRET","REFRESH_SECRET","REFRESH_TOKEN_SECRET","fetchUserWithTokenInfo","token","userInToken","verifyTokenAndFetchUser","userUuid","Promise","resolve","reject","verify","err","decoded","undefined","user","findByPk","then","persistedUser","catch","e","signAccessToken","sign","expiresIn","signRefreshToken","verifyRefreshToken","refreshToken","userTypes","newAccessToken","hashPassword","password","hash","comparePassword","plainPassword","compare","checkIfUserAlreadyRegistered","email","findOne","where","login","users","findAll","length","passwordsMatch","verifyGoogleToken","idToken","audience","verifyIdToken","error","getGoogleClientId","platform","ANDROID_GOOGLE_CLIENT_ID","IOS_GOOGLE_CLIENT_ID","WEB_GOOGLE_CLIENT_ID","generateAppleJWT","privateKey","header","alg","kid","APPLE_KID","typ","iss","APPLE_ISSUER","iat","Math","floor","Date","now","exp","aud","bid","APPLE_BUNDLE_ID","verifyAppleJwt","userData","key","getAppleSignInKey","getSigningKey","getPublicKey","verifyAppleReceipt","response","post","APPLE_SHARED_SECRET","getAppleTransactions","originalTransactionId","result","get","headers","Authorization","signedTransactions","push","decode","status","errorCode","getAppleSubscriptionsStatuses","transactionId","transaction","lastTransactions","signedTransactionInfo","signedRenewalInfo","isAppleSubscriptionActive","getSingleAppleTransactionsInfo","checkIfUserBoughtProgram","productIds","url","PURCHASE_HISTORY_URL","productId","message","getUserFromContext","ctx","state","throw","extendAppleSubscription","extendByDays","put","extendReasonCode","requestIdentifier","getAndroidAccessToken","jwtClient","auth","JWT","scopes","authorize","getAndroidSubscriptionsStatuses","credentials","ANDROID_PACKAGE_NAME","access_token","lineItems","expiryTime","toISOString","isAndroidSubscriptionsActive","subscriptionState","extendAndroidSubscription","purchaseToken","subscriptionId","packageName","currentSubResponse","currentExpiryTime","Error","currentExpiryMs","getTime","desiredExpiryMs","deferralInfo","expectedExpiryTimeMillis","desiredExpiryTimeMillis"],"mappings":"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"}
|