feature/adapt-to-new-database #34
@ -15,8 +15,6 @@ Table users {
|
||||
sex user_sex
|
||||
phone_number String
|
||||
country countries
|
||||
auth_provider String
|
||||
provider_token String
|
||||
address user_addresses
|
||||
preferences user_preferences
|
||||
created_at DateTime [default: `now()`, not null]
|
||||
@ -76,7 +74,6 @@ Table user_oauth_accounts {
|
||||
provider_token String
|
||||
refresh_token String
|
||||
expires_at DateTime
|
||||
last_login DateTime [default: `now()`, not null]
|
||||
created_at DateTime [default: `now()`, not null]
|
||||
updated_at DateTime [not null]
|
||||
user_id String [not null]
|
||||
@ -98,7 +95,7 @@ Table user_sessions {
|
||||
|
||||
Table user_preferences {
|
||||
user users [not null]
|
||||
char_as_partner characters [not null]
|
||||
char_as_partner characters
|
||||
comment_picture String
|
||||
enable_watch_history Boolean [not null, default: true]
|
||||
enable_search_history Boolean [not null, default: false]
|
||||
@ -115,7 +112,7 @@ Table user_preferences {
|
||||
rating_preferences user_rating_preferences [not null]
|
||||
country_preferences user_country_preferences [not null]
|
||||
user_id String [pk]
|
||||
char_as_partner_id String [not null]
|
||||
char_as_partner_id String
|
||||
}
|
||||
|
||||
Table user_genre_preferences {
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `auth_provider` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `provider_token` on the `users` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP COLUMN "auth_provider",
|
||||
DROP COLUMN "provider_token";
|
||||
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `last_login` on the `user_oauth_accounts` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "user_oauth_accounts" DROP COLUMN "last_login";
|
||||
@ -178,7 +178,6 @@ model UserOauthAccounts {
|
||||
provider_token String? @db.VarChar(255)
|
||||
refresh_token String? @db.VarChar(255)
|
||||
expires_at DateTime? @db.Timestamptz()
|
||||
last_login DateTime @default(now()) @db.Timestamptz()
|
||||
created_at DateTime @default(now()) @db.Timestamptz()
|
||||
updated_at DateTime @updatedAt @db.Timestamptz()
|
||||
|
||||
@ -204,7 +203,7 @@ model UserSession {
|
||||
|
||||
model UserPreference {
|
||||
user User @relation(fields: [user_id], references: [id])
|
||||
char_as_partner Character @relation(fields: [char_as_partner_id], references: [id])
|
||||
char_as_partner Character? @relation(fields: [char_as_partner_id], references: [id])
|
||||
comment_picture String? @db.VarChar(255)
|
||||
enable_watch_history Boolean @default(true)
|
||||
enable_search_history Boolean @default(false)
|
||||
@ -221,8 +220,8 @@ model UserPreference {
|
||||
rating_preferences UserRatingPreference[]
|
||||
country_preferences UserCountryPreference[]
|
||||
|
||||
user_id String @id @db.Uuid
|
||||
char_as_partner_id String @db.Uuid
|
||||
user_id String @id @db.Uuid
|
||||
char_as_partner_id String? @db.Uuid
|
||||
@@map("user_preferences")
|
||||
}
|
||||
|
||||
|
||||
@ -13,10 +13,12 @@ export const getUserHeaderInformation = (clientInfo: string): UserHeaderInformat
|
||||
const clientInfoHeader = (JSON.parse(clientInfo) as ClientInfoHeader) ?? ("unknown" as string);
|
||||
|
||||
const userHeaderInformation = {
|
||||
ip: clientInfoHeader.ip ?? "unknown",
|
||||
deviceType: clientInfoHeader.deviceType ?? "unknown",
|
||||
deviceOS: (clientInfoHeader.os ?? "unknown") + " " + (clientInfoHeader.osVersion ?? "unknown"),
|
||||
browser: (clientInfoHeader.browser ?? "unknown") + " " + (clientInfoHeader.browserVersion ?? "unknown"),
|
||||
ip: clientInfoHeader.ip,
|
||||
deviceType: clientInfoHeader.deviceType ?? "desktop",
|
||||
osType: clientInfoHeader.os ?? "unknown",
|
||||
osVersion: clientInfoHeader.osVersion ?? "unknown",
|
||||
browserName: clientInfoHeader.browser ?? "unknown",
|
||||
browserVersion: clientInfoHeader.browserVersion ?? "unknown",
|
||||
};
|
||||
|
||||
return userHeaderInformation;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
export interface UserHeaderInformation {
|
||||
ip: string;
|
||||
ip?: string;
|
||||
deviceType: string;
|
||||
deviceOS: string;
|
||||
browser: string;
|
||||
osType: string;
|
||||
osVersion: string;
|
||||
browserName: string;
|
||||
browserVersion: string;
|
||||
}
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { prisma } from "../../../../utils/databases/prisma/connection";
|
||||
|
||||
export const findAuthIdentityByEmailAndProviderRepository = async (email: string) => {
|
||||
try {
|
||||
return await prisma.user.findUnique({
|
||||
where: {
|
||||
email: email,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
oauth_accounts: {
|
||||
select: {
|
||||
provider_sub: true,
|
||||
provider_name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
throw new AppError(500, "Error finding user by email", error);
|
||||
}
|
||||
};
|
||||
@ -12,14 +12,12 @@ export const googleCallbackService = async (
|
||||
code: string;
|
||||
callbackURI?: string;
|
||||
},
|
||||
userHeaderInfo: UserHeaderInformation
|
||||
userHeaderInfo: UserHeaderInformation,
|
||||
) => {
|
||||
try {
|
||||
// get code and state for validation from params and search for state in redis cache
|
||||
const state = query.state;
|
||||
const codeVerifier = await redis.get(
|
||||
`${process.env.APP_NAME}:pkce:${state}`
|
||||
);
|
||||
const codeVerifier = await redis.get(`${process.env.APP_NAME}:pkce:${state}`);
|
||||
|
||||
// return error if the state for validation is not found in redis, and delete if found
|
||||
if (!codeVerifier) throw new AppError(408, "Request timeout");
|
||||
@ -27,21 +25,15 @@ export const googleCallbackService = async (
|
||||
|
||||
// create access token with the result of validating the authorization code that compares access code with validator state
|
||||
const google = googleProvider(query.callbackURI);
|
||||
const tokens = await google.validateAuthorizationCode(
|
||||
query.code,
|
||||
codeVerifier
|
||||
);
|
||||
const tokens = await google.validateAuthorizationCode(query.code, codeVerifier);
|
||||
|
||||
// get user data from Google using the access token that has been created.
|
||||
const accessToken = tokens.accessToken();
|
||||
const response = await fetch(
|
||||
"https://openidconnect.googleapis.com/v1/userinfo",
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
// parse the user data response
|
||||
const userData = (await response.json()) as GoogleCallbackUserData;
|
||||
@ -49,19 +41,17 @@ export const googleCallbackService = async (
|
||||
// Provision or authenticate the user in the system
|
||||
return await OAuthUserProvisionService(
|
||||
{
|
||||
provider: "google",
|
||||
providerId: userData.sub,
|
||||
providerToken: accessToken,
|
||||
providerPayload: userData,
|
||||
fullname: userData.name,
|
||||
username: `gle_${userData.sub}`,
|
||||
email: userData.email,
|
||||
username: `goo_${userData.sub}`,
|
||||
name: userData.name,
|
||||
avatar: userData.picture,
|
||||
password: Math.random()
|
||||
.toString(36)
|
||||
.slice(2, 16),
|
||||
oauthProvider: {
|
||||
providerName: "google",
|
||||
sub: userData.sub,
|
||||
token: accessToken,
|
||||
},
|
||||
},
|
||||
userHeaderInfo
|
||||
userHeaderInfo,
|
||||
);
|
||||
} catch (error) {
|
||||
ErrorForwarder(error, 500, "Authentication service error");
|
||||
|
||||
@ -1,39 +1,29 @@
|
||||
import { User } from "@prisma/client";
|
||||
import { UserHeaderInformation } from "../../../../helpers/http/userHeader/getUserHeaderInformation/types";
|
||||
import { findUserService } from "../../../user/services/internal/findUser.service";
|
||||
import { createUserSessionService } from "../../../userSession/services/createUserSession.service";
|
||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { createUserViaOauth } from "../../../user/user.types";
|
||||
import { createUserService } from "../../../user/services/internal/createUser.service";
|
||||
import { AppError } from "../../../../helpers/error/instances/app";
|
||||
import { findAuthIdentityByEmailAndProviderRepository } from "../../repositories/READ/findAuthIdentityByEmailAndProvider.repository";
|
||||
|
||||
export const OAuthUserProvisionService = async (
|
||||
payload: createUserViaOauth,
|
||||
userHeaderInfo: UserHeaderInformation
|
||||
) => {
|
||||
export const OAuthUserProvisionService = async (payload: createUserViaOauth, userHeaderInfo: UserHeaderInformation) => {
|
||||
try {
|
||||
const providerId = payload.providerId;
|
||||
const findUserResult = (await findUserService({
|
||||
identifier: providerId,
|
||||
queryTarget: "providerId",
|
||||
options: { verbosity: "full" },
|
||||
})) as User;
|
||||
|
||||
if (findUserResult) {
|
||||
return await createUserSessionService(findUserResult.id, userHeaderInfo);
|
||||
} else {
|
||||
const findUserByEmailOnly = await findUserService({
|
||||
identifier: payload.email,
|
||||
queryTarget: "email",
|
||||
options: { verbosity: "exist" },
|
||||
});
|
||||
|
||||
if (findUserByEmailOnly)
|
||||
throw new AppError(409, "Email already in use with another account");
|
||||
const checkExistingUser = await findAuthIdentityByEmailAndProviderRepository(payload.email);
|
||||
|
||||
if (
|
||||
checkExistingUser &&
|
||||
checkExistingUser.oauth_accounts.some((account) => account.provider_sub === payload.oauthProvider.sub)
|
||||
) {
|
||||
// User already exists with this OAuth provider
|
||||
return await createUserSessionService(checkExistingUser.id, userHeaderInfo);
|
||||
} else if (!checkExistingUser) {
|
||||
// No user with this email, create new user
|
||||
const createdUser = await createUserService(payload);
|
||||
return await createUserSessionService(createdUser.id, userHeaderInfo);
|
||||
}
|
||||
|
||||
// User exists with this email but not with this OAuth provider
|
||||
throw new AppError(409, "Email already in use with another account");
|
||||
} catch (error) {
|
||||
ErrorForwarder(error);
|
||||
}
|
||||
|
||||
@ -3,18 +3,14 @@ import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { userModel } from "../../user.model";
|
||||
import { createUserViaRegisterInput } from "../../user.types";
|
||||
|
||||
export const createUserViaRegisterRepository = async (
|
||||
payload: createUserViaRegisterInput,
|
||||
) => {
|
||||
export const createUserViaRegisterRepository = async (payload: createUserViaRegisterInput) => {
|
||||
try {
|
||||
return await userModel.create({
|
||||
data: {
|
||||
...payload,
|
||||
id: generateUUIDv7(),
|
||||
preference: {
|
||||
create: {
|
||||
id: generateUUIDv7(),
|
||||
},
|
||||
preferences: {
|
||||
create: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,21 +1,17 @@
|
||||
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
|
||||
import { hashPassword } from "../../../../helpers/security/password/hash";
|
||||
import { createUserViaRegisterRepository } from "../../repositories/create/createUserViaRegister.repository";
|
||||
import {
|
||||
createUserViaOauth,
|
||||
createUserViaRegisterInput,
|
||||
} from "../../user.types";
|
||||
import { createUserViaOauth, createUserViaRegisterInput } from "../../user.types";
|
||||
|
||||
export const createUserService = async (
|
||||
payload: createUserViaRegisterInput | createUserViaOauth
|
||||
) => {
|
||||
export const createUserService = async (payload: createUserViaRegisterInput) => {
|
||||
try {
|
||||
const hashedPassword = await hashPassword(payload.password);
|
||||
if ("password" in payload && payload.password)
|
||||
return await createUserViaRegisterRepository({
|
||||
...payload,
|
||||
password: await hashPassword(payload.password),
|
||||
} as createUserViaRegisterInput);
|
||||
|
||||
return await createUserViaRegisterRepository({
|
||||
...payload,
|
||||
password: hashedPassword,
|
||||
});
|
||||
return await createUserViaRegisterRepository(payload as Omit<createUserViaOauth, "oauthProvider">);
|
||||
} catch (error) {
|
||||
ErrorForwarder(error);
|
||||
}
|
||||
|
||||
@ -10,20 +10,20 @@ export interface getUserDataOptions {
|
||||
export type getUserDataIncludeOptions = "preference" | "roles";
|
||||
|
||||
export interface createUserViaRegisterInput {
|
||||
name: string;
|
||||
fullname: string;
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
export interface createUserViaOauth {
|
||||
provider: string;
|
||||
providerId: string;
|
||||
providerToken?: string;
|
||||
providerPayload?: unknown;
|
||||
email: string;
|
||||
username: string;
|
||||
name: string;
|
||||
password?: string;
|
||||
avatar?: string;
|
||||
bio?: string;
|
||||
password: string;
|
||||
datebirth?: Date;
|
||||
}
|
||||
export interface createUserViaOauth extends createUserViaRegisterInput {
|
||||
oauthProvider: {
|
||||
providerName: string;
|
||||
sub: string;
|
||||
token: string;
|
||||
refreshToken?: string;
|
||||
expiresAt?: Date;
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,24 +5,21 @@ import { createUserSessionRepository } from "../repositories/createUserSession.r
|
||||
import { jwtEncode } from "../../../helpers/http/jwt/encode";
|
||||
import { createUserSessionInRedisService } from "./internal/createUserSessionInRedis.service";
|
||||
|
||||
export const createUserSessionService = async (
|
||||
userId: string,
|
||||
userHeaderInfo: UserHeaderInformation,
|
||||
) => {
|
||||
export const createUserSessionService = async (userId: string, userHeaderInfo: UserHeaderInformation) => {
|
||||
try {
|
||||
// set the date when the token will expire
|
||||
const generateTokenExpirationDate =
|
||||
Date.now() + Number(process.env.SESSION_EXPIRE) * 1000;
|
||||
const generateTokenExpirationDate = Date.now() + Number(process.env.SESSION_EXPIRE) * 1000;
|
||||
|
||||
// construct all data to fit the user session input query
|
||||
const constructData = {
|
||||
userId,
|
||||
isAuthenticated: true,
|
||||
deviceType: userHeaderInfo.deviceType,
|
||||
deviceOs: userHeaderInfo.deviceOS,
|
||||
deviceIp: userHeaderInfo.ip,
|
||||
browser: userHeaderInfo.browser,
|
||||
validUntil: new Date(generateTokenExpirationDate),
|
||||
user_id: userId,
|
||||
device_type: userHeaderInfo.deviceType,
|
||||
os_type: userHeaderInfo.osType,
|
||||
os_version: userHeaderInfo.osVersion,
|
||||
browser_name: userHeaderInfo.browserName,
|
||||
browser_version: userHeaderInfo.browserVersion,
|
||||
ip_login: userHeaderInfo.ip,
|
||||
valid_until: new Date(generateTokenExpirationDate),
|
||||
} as Prisma.UserSessionUncheckedCreateInput;
|
||||
|
||||
// insert user session into database
|
||||
|
||||
Reference in New Issue
Block a user