/home/bdqbpbxa/dev-subdomains/admin.pixory.goodface.com.ua/src/policies/auth0.ts
import utils from"@strapi/utils";
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import axios from 'axios';
import { Context } from 'koa';
import { env } from 'src/config/env';
import { getIpRegion } from "src/api/utils/localization/services/localization";
const { PolicyError } = utils.errors;
interface UserInfo {
sub: string;
email: string;
email_verified: boolean;
}
const client = jwksClient({
jwksUri: `${env.auth0.issuerBaseUrl}.well-known/jwks.json`,
});
function verifyTokenAsync(token: string, audience: string, issuer: string): Promise<any> {
return new Promise((resolve, reject) => {
jwt.verify(
token,
(header, callback) => {
if (!header.kid) {
return callback(new Error('Missing kid in token header'));
}
client.getSigningKey(header.kid, function (err, key) {
if (err) return callback(err);
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
},
{
audience,
issuer,
algorithms: ['RS256'],
},
(err, decoded) => {
if (err) return reject(err);
resolve(decoded);
}
);
});
}
async function getUserInfo(accessToken: string): Promise<UserInfo> {
try {
const response = await axios.get(`${env.auth0.issuerBaseUrl}userinfo`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
} catch (error) {
throw error;
}
}
async function createUserFromAuth0(ctx: Context, userInfo: UserInfo, strapi: any) {
const authenticatedRole = await strapi.query('plugin::users-permissions.role').findOne({
where: { type: 'authenticated' }
});
const userIp = ctx.request.ip || (ctx.request.headers['x-forwarded-for'] as string)?.split(',')[0];
const ipData = await getIpRegion(userIp);
return await strapi.query('plugin::users-permissions.user').create({
data: {
sub: userInfo.sub,
email: userInfo.email,
confirmed: userInfo.email_verified,
username: userInfo.email.split('@')[0],
provider: userInfo.sub.split('|')[0],
currency: ipData.currency,
timezone: ipData.timezone,
role: authenticatedRole.id,
},
});
}
const auth0Policy = async (ctx: Context, config, { strapi }) => {
try {
const authHeader = ctx.request.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new PolicyError('Authorization header is missing or invalid', { status: 401 });
}
const token = authHeader.split(' ')[1];
const decoded = await verifyTokenAsync(
token,
env.auth0.audience,
env.auth0.issuerBaseUrl
);
let user = await strapi.db.query('plugin::users-permissions.user').findOne({
where: { sub: decoded.sub }
});
if (!user) {
const authHeader = ctx.request.headers.authorization;
const accessToken = authHeader.split(' ')[1];
const userInfo = await getUserInfo(accessToken);
user = await createUserFromAuth0(ctx, userInfo, strapi);
}
ctx.state.user = {
id: user.id,
sub: decoded.sub,
...decoded,
};
return true;
} catch (error) {
if (error instanceof PolicyError) {
throw error;
}
console.log(error)
if (error.name === 'JsonWebTokenError') {
throw new PolicyError('Invalid token format', { status: 401 });
}
if (error.name === 'TokenExpiredError') {
throw new PolicyError('Token has expired', { status: 401 });
}
if (error.name === 'NotBeforeError') {
throw new PolicyError('Token not active yet', { status: 401 });
}
// Handle JWKS errors
if (error.message && error.message.includes('kid')) {
throw new PolicyError('Invalid token key identifier', { status: 401 });
}
// Generic error fallback
strapi.log.error('Auth0 policy error:', error);
throw new PolicyError('Authentication failed', { status: 401 });
}
};
export default auth0Policy;