Я создал уровень аутентификации REST перед моим GraphQL API, я хотел бы знать, достаточно ли он безопасен или я могу что-то улучшить, я добавлю больше поставщиков аутентификации, в конце концов этот API будет обслуживать веб-приложение, используя продемонстрированный вход в Google и приложение React Native с электронной почтой, SMS, Facebook и Apple auth
// index.ts
server
.register(fastifyCors, {
origin: process.env.CORS_ORIGIN,
credentials: true,
})
.register(fastifyCookie)
.register(fastifySession, {
cookieName: "fid",
store: new RedisStore({
client: new Redis(process.env.REDIS_URI),
ttl: SESSION_TTL,
}),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax",
secure: true,
},
saveUninitialized: false,
secret: process.env.SESSION_SECRET,
})
.register(fastifyPassport.initialize())
.register(apolloServer.createHandler({ cors: false }))
.register(authRoutes)
// setupPassport.ts
export const setupPassport = (prisma: PrismaClient) => {
fastifyPassport.registerUserSerializer<User, User["id"]>(
async (user) => user.id
)
fastifyPassport.registerUserDeserializer<User["id"], User>(async (id) => {
const user = await prisma.user.findUnique({
where: {
id,
},
})
if (!user) {
throw new Error("No user")
}
return user
})
fastifyPassport.use(
new Strategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:4000/auth/google/callback",
},
async (_accessToken, _refreshToken, profile, done) => {
const existingUser = await prisma.identity.findFirst({
where: {
provider: "GOOGLE",
providerId: profile.id,
},
include: {
user: true,
},
})
if (existingUser) {
return done(undefined, existingUser)
}
if (!profile.emails?.length || !profile.name) {
throw new Error("No email or name")
}
const newUser = await prisma.user.create({
data: {
...profile.name,
email: profile.emails[0].value,
identities: {
create: [{ provider: "GOOGLE", providerId: profile.id }],
},
},
})
return done(undefined, newUser)
}
)
)
}
// authRoutes.ts
export const authRoutes: FastifyPluginCallback = (fastify, _opts, done) => {
fastify.get(
"/auth/google",
{
preValidation: fastifyPassport.authenticate("google", {
scope: ["profile", "email"],
}),
},
async (request, reply) => {}
)
fastify.get(
"/auth/google/callback",
{
preValidation: fastifyPassport.authorize("google", { session: false }),
},
async (request, reply) => {}
)
done()
}
// permissions.ts
import { shield } from "graphql-shield"
export const permissions = shield({
Query: {},
Mutation: {},
ShopLocation: rules.isAuthenticatedUser,
})
// rules.ts
import { rule } from "graphql-shield"
export const rules = {
isAuthenticatedUser: rule()(async (_parent, _args, ctx: Context) => {
return ctx.request.isAuthenticated()
}),
isAdmin: rule()(async (_parent, _args, ctx: Context) => {
return ctx.request.user.role === Role.ADMIN
}),
isOperator: rule()(async (_parent, _args, ctx: Context) => {
return ctx.request.user.role === Role.OPERATOR
}),
isUser: rule()(async (_parent, _args, ctx: Context) => {
return ctx.request.user.role === Role.USER
}),
}