mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-18 12:47:56 +02:00
Connexion Facebook / LinkedIn en OAuth depuis le backoffice
Les jetons se posaient à la main dans docker-compose. Un écran Backend ›
Réseaux sociaux permet désormais de coller les identifiants de l'application,
cliquer Connecter, choisir la page, et ne plus y revenir.
- Table `social_accounts` : identifiants, jetons et cible par réseau. Tous les
secrets sont chiffrés en AES-256-GCM (`src/lib/crypto.ts`), clé
`SOCIAL_TOKEN_KEY` avec repli sur `AUTH_SECRET`.
- Routes `api/social/[network]/{connect,callback}` : `state` anti-CSRF en cookie
httpOnly, échange du code, récupération des pages administrées. Aucun jeton ne
transite par une URL — les cibles sont relistées côté serveur au moment de la
sélection.
- Facebook : jeton utilisateur longue durée puis jeton de PAGE, qui n'expire
pas. LinkedIn : jeton 60 jours, rafraîchi automatiquement si l'application a
obtenu les jetons de rafraîchissement programmatiques.
- Faute de quoi le backoffice affiche la date d'expiration, un bandeau sur le
tableau de bord à J-7 et un bouton Reconnecter.
- `social.ts` lit les identifiants via `social-accounts.ts` : base d'abord,
variables d'environnement ensuite. Les installations existantes continuent de
fonctionner sans modification.
- Le secret d'application n'est jamais renvoyé au navigateur : champ vide =
valeur conservée.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
This commit is contained in:
19 files changed
+2599
-82
No files matched your search
@@ -131,6 +131,25 @@ export const socialPosts = pgTable(
|
||||
})
|
||||
);
|
||||
|
||||
// ---- Comptes réseaux sociaux connectés (OAuth) ----
|
||||
// Une ligne par réseau. Les secrets sont chiffrés (src/lib/crypto.ts) et ne
|
||||
// ressortent jamais vers le navigateur.
|
||||
export const socialAccounts = pgTable("social_accounts", {
|
||||
id: serial("id").primaryKey(),
|
||||
network: socialNetworkEnum("network").notNull().unique(),
|
||||
appId: varchar("app_id", { length: 200 }).notNull(),
|
||||
appSecret: text("app_secret").notNull(),
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
// Nul = n'expire pas (jeton de page Facebook).
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
||||
targetId: varchar("target_id", { length: 200 }),
|
||||
targetName: varchar("target_name", { length: 200 }),
|
||||
connectedById: integer("connected_by_id").references(() => users.id, { onDelete: "set null" }),
|
||||
connectedAt: timestamp("connected_at", { withTimezone: true }),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
// ---- Membership requests (demandes d'adhésion) ----
|
||||
export const membershipRequests = pgTable("membership_requests", {
|
||||
id: serial("id").primaryKey(),
|
||||
@@ -316,6 +335,7 @@ export type Member = typeof members.$inferSelect;
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type Promotion = typeof promotions.$inferSelect;
|
||||
export type SocialPost = typeof socialPosts.$inferSelect;
|
||||
export type SocialAccount = typeof socialAccounts.$inferSelect;
|
||||
export type SocialNetwork = (typeof socialNetworkEnum.enumValues)[number];
|
||||
export type MembershipRequest = typeof membershipRequests.$inferSelect;
|
||||
export type ContactMessage = typeof contactMessages.$inferSelect;
|
||||
|
||||
Reference in new issue
Block a user