From f90c078301268257668a393fbb47f2022ea373cc Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Fri, 15 May 2026 23:37:10 -0400 Subject: [PATCH 01/16] oidc mostly in place, hub extraction mostly functioning --- .../CommunityCreate/CommunityCreate.tsx | 39 +- .../CommunityAdminSettings.tsx | 3 + .../CommunitySettings/TransferOwnership.tsx | 154 +++ server/community/model.ts | 4 + server/community/queries.ts | 1 + server/kf/api.ts | 990 ++++++++++++++++++ server/kf/auth.ts | 150 +++ server/routes/communityCreate.tsx | 6 +- server/routes/index.ts | 12 +- server/routes/login.kf.tsx | 16 + server/routes/passwordReset.kf.tsx | 17 + server/routes/signup.kf.tsx | 29 + .../2026_05_15_addKfOrgIdToCommunities.js | 17 + .../2026_06_15_makeKfOrgIdNotNull.js | 33 + .../2026_06_15_removePasswordColumns.js | 35 + utils/api/schemas/community.ts | 2 + 16 files changed, 1500 insertions(+), 8 deletions(-) create mode 100644 client/containers/DashboardSettings/CommunitySettings/TransferOwnership.tsx create mode 100644 server/kf/api.ts create mode 100644 server/kf/auth.ts create mode 100644 server/routes/login.kf.tsx create mode 100644 server/routes/passwordReset.kf.tsx create mode 100644 server/routes/signup.kf.tsx create mode 100644 tools/migrations/2026_05_15_addKfOrgIdToCommunities.js create mode 100644 tools/migrations/2026_06_15_makeKfOrgIdNotNull.js create mode 100644 tools/migrations/2026_06_15_removePasswordColumns.js diff --git a/client/containers/CommunityCreate/CommunityCreate.tsx b/client/containers/CommunityCreate/CommunityCreate.tsx index 79609a920..b5cba2f5f 100644 --- a/client/containers/CommunityCreate/CommunityCreate.tsx +++ b/client/containers/CommunityCreate/CommunityCreate.tsx @@ -82,10 +82,19 @@ const CommunityCreatedView = ({ subdomain, hubName }: { subdomain: string; hubNa ); }; +type KFOrg = { + id: string; + name: string; + slug: string; + type: 'personal' | 'shared'; + role: string; +}; + type Props = { hubData?: Hub | null; templates?: CommunityTemplate[]; hubCommunities?: { id: string; title: string; subdomain: string; avatar?: string | null }[]; + kfOrgs?: KFOrg[]; }; const HubBrandedHeader = ({ hub }: { hub: Hub }) => { @@ -109,7 +118,7 @@ const HubBrandedHeader = ({ hub }: { hub: Hub }) => { }; const CommunityCreate = (props: Props) => { - const { hubData, templates = [], hubCommunities = [] } = props; + const { hubData, templates = [], hubCommunities = [], kfOrgs = [] } = props; const { loginData, locationData } = usePageContext(); const altchaRef = useRef(null); const hubSlug = hubData?.slug || locationData?.query?.hub || null; @@ -126,6 +135,12 @@ const CommunityCreate = (props: Props) => { const [selectedTemplateId, setSelectedTemplateId] = useState(null); const [cloneCommunityId, setCloneCommunityId] = useState(null); + // KF org picker: default to personal org, or first available + const personalOrg = kfOrgs.find((o) => o.type === 'personal'); + const [selectedKfOrgId, setSelectedKfOrgId] = useState( + personalOrg?.id ?? kfOrgs[0]?.id ?? null, + ); + const hasHub = !!hubData; const hubAccentDark = hubData?.accentColorDark || '#2D2E2F'; @@ -163,6 +178,7 @@ const CommunityCreate = (props: Props) => { ...(selectedTemplateId === CLONE_MARKER && cloneCommunityId ? { cloneCommunityId } : {}), + ...(selectedKfOrgId ? { kfOrgId: selectedKfOrgId } : {}), }); setCreateIsLoading(false); setIsCreated(true); @@ -308,6 +324,27 @@ const CommunityCreate = (props: Props) => { onChange={onDescriptionChange} helperText={`${description.length}/280 characters`} /> + {kfOrgs.length > 1 && ( + +
+ +
+
+ )} {selectedTemplateId ? ( { + + ); diff --git a/client/containers/DashboardSettings/CommunitySettings/TransferOwnership.tsx b/client/containers/DashboardSettings/CommunitySettings/TransferOwnership.tsx new file mode 100644 index 000000000..cfb270367 --- /dev/null +++ b/client/containers/DashboardSettings/CommunitySettings/TransferOwnership.tsx @@ -0,0 +1,154 @@ +import React, { useCallback, useEffect, useState } from 'react'; + +import { Button, Callout, Classes } from '@blueprintjs/core'; + +import { apiFetch } from 'client/utils/apiFetch'; +import { SettingsSection } from 'components'; + +type KFOrg = { + id: string; + name: string; + slug: string; + type: 'personal' | 'shared'; + role: string; +}; + +type Props = { + communityData: { + id: string; + title: string; + kfOrgId: string | null; + }; +}; + +const TransferOwnership = (props: Props) => { + const { communityData } = props; + const [orgs, setOrgs] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedOrgId, setSelectedOrgId] = useState(null); + const [isTransferring, setIsTransferring] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + + const loadOrgs = useCallback(async () => { + try { + const data = await apiFetch.get('/api/kf/my-orgs'); + const fetchedOrgs: KFOrg[] = data.orgs ?? []; + setOrgs(fetchedOrgs); + // Default to current org if set, otherwise first org + if (communityData.kfOrgId && fetchedOrgs.some((o) => o.id === communityData.kfOrgId)) { + setSelectedOrgId(communityData.kfOrgId); + } else if (fetchedOrgs.length > 0) { + setSelectedOrgId(fetchedOrgs[0].id); + } + } catch { + setError('Failed to load organizations'); + } finally { + setLoading(false); + } + }, [communityData.kfOrgId]); + + useEffect(() => { + loadOrgs(); + }, [loadOrgs]); + + const selectedOrg = orgs.find((o) => o.id === selectedOrgId); + const isCurrentOrg = selectedOrgId === communityData.kfOrgId; + + const handleTransfer = async () => { + if (!selectedOrgId || isCurrentOrg) return; + setIsTransferring(true); + setError(null); + setSuccess(null); + try { + await apiFetch.post('/api/kf/transfer-community', { + communityId: communityData.id, + kfOrgId: selectedOrgId, + }); + setSuccess( + `Community transferred to ${selectedOrg?.name ?? 'the selected organization'}.`, + ); + // Update the local state so the button disables + communityData.kfOrgId = selectedOrgId; + } catch (err: any) { + setError(err?.error || err?.message || 'Failed to transfer community'); + } finally { + setIsTransferring(false); + } + }; + + if (loading) { + return ( + +

Loading organizations...

+
+ ); + } + + // Need at least 2 orgs to have somewhere to transfer to + if (orgs.length < 2) { + return null; + } + + const currentOrg = orgs.find((o) => o.id === communityData.kfOrgId); + + return ( + +

+ Transfer this community to a different KF Account. The target account will become + the billing owner of this community. +

+ + {currentOrg && ( +

+ Currently owned by: {currentOrg.name} + {currentOrg.type === 'personal' ? ' (Personal)' : ''} +

+ )} + + {error && ( + + {error} + + )} + + {success && ( + + {success} + + )} + +
+
+
+ +
+
+
+
+ ); +}; + +export default TransferOwnership; diff --git a/server/community/model.ts b/server/community/model.ts index ac2d30609..26cd2cd81 100644 --- a/server/community/model.ts +++ b/server/community/model.ts @@ -241,6 +241,10 @@ export class Community extends Model< @Column(DataType.UUID) declare templateId: string | null; + /** KF Auth organization that owns this community (for billing/ownership) */ + @Column(DataType.TEXT) + declare kfOrgId: string | null; + @BelongsTo(() => CommunityTemplate, { as: 'template', foreignKey: 'templateId', diff --git a/server/community/queries.ts b/server/community/queries.ts index b1ec5c8f6..2f7d3fac0 100644 --- a/server/community/queries.ts +++ b/server/community/queries.ts @@ -88,6 +88,7 @@ export const createCommunity = async ( accentColorDark: inputValues.accentColorDark ?? '#000000', navigation: [{ type: 'page', id: homePageId }], hideCreatePubButton: true, + ...(inputValues.kfOrgId ? { kfOrgId: inputValues.kfOrgId } : {}), }, { actorId: userData.id }, ); diff --git a/server/kf/api.ts b/server/kf/api.ts new file mode 100644 index 000000000..8be970531 --- /dev/null +++ b/server/kf/api.ts @@ -0,0 +1,990 @@ +/** + * KF Auth integration routes for PubPub. + * + * OIDC login/callback: + * GET /auth/login — redirect to KF Auth + * GET /auth/callback — handle OIDC callback, create session + * POST /auth/logout — clear session + redirect to KF Auth logout + * + * Internal service-to-service endpoints (KF_INTERNAL_API_KEY): + * POST /api/kf/profile-sync — receive profile updates from KF Auth + * GET /api/kf/branding — return community branding for login page + * GET /api/kf/summary — return community list for a KF org + * GET /api/kf/billing/usage — return usage stats for billing (placeholder) + * + * Session-authenticated endpoints: + * GET /api/kf/my-orgs — return current user's KF Account memberships + * POST /api/kf/transfer-community — transfer community ownership to a different KF Account + */ + +import { promisify } from 'util'; +import { timingSafeEqual } from 'crypto'; + +import { Router } from 'express'; + +import { Community, Collection, Member, Pub, PubAttribution, Release, User } from 'server/models'; +import { sequelize } from 'server/sequelize'; +import { ensureUserIsCommunityAdmin } from 'utils/ensureUserIsCommunityAdmin'; +import { getHashedUserId } from 'utils/caching/getHashedUserId'; +import { isProd, isDuqDuq } from 'utils/environment'; + +import { + buildAuthorizeUrl, + exchangeCode, + fetchUserInfo, + fetchUserOrgs, + KF_AUTH_URL, +} from './auth'; + +// ── Helpers ────────────────────────────────────────────────────────── + +const KF_INTERNAL_API_KEY = process.env.KF_INTERNAL_API_KEY; + +function requireInternalKey( + req: any, + res: any, + next: () => void, +): void { + if (!KF_INTERNAL_API_KEY) { + res.status(500).json({ error: 'KF_INTERNAL_API_KEY not configured' }); + return; + } + const auth = req.headers.authorization; + const expected = `Bearer ${KF_INTERNAL_API_KEY}`; + // Use timing-safe comparison to prevent timing attacks on the API key + if ( + !auth || + auth.length !== expected.length || + !timingSafeEqual(Buffer.from(auth), Buffer.from(expected)) + ) { + res.status(401).json({ error: 'Unauthorized' }); + return; + } + next(); +} + +/** + * Derive the community hostname the user came from. + * Needed because the OIDC callback always hits the main domain. + */ +function getCommunityHost(req: any): string { + // Use the communityHostname header if set by the reverse proxy, + // otherwise fall back to the raw hostname. + return req.headers.communityhostname || req.hostname; +} + +// Cookie name for OIDC state (verifier stored in session for custom domain compat) +const STATE_COOKIE = 'kf_oauth_state'; + +// ── Router ─────────────────────────────────────────────────────────── + +export const router = Router(); + +// ─── OIDC login ────────────────────────────────────────────────────── + +router.get('/auth/login', (req: any, res: any) => { + const communityHost = getCommunityHost(req); + const returnTo = req.query.return_to || '/'; + + // Encode the community hostname + return path in state so we can + // redirect back after the OIDC callback. + const statePayload = JSON.stringify({ host: communityHost, returnTo }); + const stateToken = Buffer.from(statePayload).toString('base64url'); + + const { url, codeVerifier } = buildAuthorizeUrl(stateToken); + + const cookieOpts = { + httpOnly: true, + secure: isProd(), + sameSite: 'lax' as const, + path: '/', + maxAge: 600_000, // 10 minutes + // Set on .pubpub.org so the callback (on www.pubpub.org) can read it + ...(isProd() && + communityHost.indexOf('pubpub.org') > -1 && { + domain: '.pubpub.org', + }), + }; + + res.cookie(STATE_COOKIE, stateToken, cookieOpts); + + // Store verifier in session (not cookie) so it works across domains. + // Custom domain sessions are scoped to their domain, and the callback + // hits the same domain since PubPub proxies all requests. + req.session.kfOauthVerifier = codeVerifier; + req.session.save(() => { + return res.redirect(url); + }); +}); + +// ─── OIDC callback ─────────────────────────────────────────────────── + +router.get('/auth/callback', async (req: any, res: any) => { + try { + const { code, state, error } = req.query; + + if (error) { + console.error('KF Auth error:', error, req.query.error_description); + return res.redirect('/login?error=auth_failed'); + } + + if (!code || !state) { + return res.redirect('/login?error=missing_params'); + } + + // Validate state + const savedState = req.cookies[STATE_COOKIE]; + const codeVerifier = req.session?.kfOauthVerifier; + + // Clear OIDC state + res.clearCookie(STATE_COOKIE, { path: '/' }); + if (req.session) { + delete req.session.kfOauthVerifier; + } + + if (!savedState || savedState !== state) { + return res.redirect('/login?error=invalid_state'); + } + + if (!codeVerifier) { + return res.redirect('/login?error=missing_verifier'); + } + + // Exchange authorization code for tokens + const tokens = await exchangeCode(code, codeVerifier); + + // Fetch user info from KF Auth + const userInfo = await fetchUserInfo(tokens.access_token); + const kfUserId = userInfo.sub; + + // Look up PubPub user by ID (PubPub ID = KF Auth ID after seeding) + const user = await User.findOne({ where: { id: kfUserId } }); + + if (!user) { + console.error(`No PubPub user found for KF Auth ID: ${kfUserId}`); + return res.redirect('/login?error=user_not_found'); + } + + // Create a standard Passport session (indistinguishable from old login) + const logIn = promisify(req.logIn.bind(req)); + await logIn(user); + + // Set the CDN cache cookie + const hashedUserId = getHashedUserId(user); + res.cookie('pp-lic', `pp-li-${hashedUserId}`, { + ...(isProd() && + req.hostname.indexOf('pubpub.org') > -1 && { + domain: '.pubpub.org', + }), + ...(isDuqDuq() && + req.hostname.indexOf('pubpub.org') > -1 && { + domain: '.duqduq.org', + }), + maxAge: 30 * 24 * 60 * 60 * 1000, + }); + + // Parse state to get the community host + return path + let redirectUrl = '/'; + try { + const statePayload = JSON.parse( + Buffer.from(state, 'base64url').toString(), + ); + const host = statePayload.host || ''; + const returnTo = statePayload.returnTo || '/'; + + if (host && host !== req.hostname) { + // Redirect back to the community the user came from + const protocol = isProd() ? 'https' : 'http'; + redirectUrl = `${protocol}://${host}${returnTo}`; + } else { + redirectUrl = returnTo; + } + } catch { + // If state parsing fails, just go to root + redirectUrl = '/'; + } + + return res.redirect(redirectUrl); + } catch (err) { + console.error('OIDC callback error:', err); + return res.redirect('/login?error=callback_failed'); + } +}); + +// ─── Logout ────────────────────────────────────────────────────────── + +router.post('/auth/logout', (req: any, res: any) => { + // Clear local session + req.logout(() => { + // Set pp-lic to logged-out state + res.cookie('pp-lic', 'pp-lo', { + ...(isProd() && + req.hostname.indexOf('pubpub.org') > -1 && { + domain: '.pubpub.org', + }), + maxAge: 30 * 24 * 60 * 60 * 1000, + }); + + // Redirect to KF Auth's logout endpoint so the SSO session is also cleared + const returnUrl = `${process.env.APP_URL || 'http://localhost:9876'}/`; + return res.redirect( + `${KF_AUTH_URL}/api/auth/sign-out?callbackURL=${encodeURIComponent(returnUrl)}`, + ); + }); +}); + +// ─── Profile sync (webhook from KF Auth) ───────────────────────────── + +router.post('/api/kf/profile-sync', requireInternalKey, async (req: any, res: any) => { + try { + const { userId, givenName, familyName, displayName, email, image } = + req.body; + + if (!userId) { + return res.status(400).json({ error: 'userId is required' }); + } + + const user = await User.findOne({ where: { id: userId } }); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + const updates: Record = {}; + if (displayName !== undefined) updates.fullName = displayName; + if (givenName !== undefined) updates.firstName = givenName; + if (familyName !== undefined) updates.lastName = familyName; + if (email !== undefined) updates.email = email.toLowerCase(); + if (image !== undefined) updates.avatar = image; + + // Recalculate initials when name changes + if (givenName !== undefined || familyName !== undefined || displayName !== undefined) { + const first = givenName ?? user.firstName ?? ''; + const last = familyName ?? user.lastName ?? ''; + if (first || last) { + updates.initials = `${first.charAt(0)}${last.charAt(0)}`.toUpperCase(); + } + } + + if (Object.keys(updates).length > 0) { + await user.update(updates); + } + + return res.status(200).json({ ok: true }); + } catch (err) { + console.error('Profile sync error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── Branding API (for KF Auth login page) ─────────────────────────── + +router.get('/api/kf/branding', requireInternalKey, async (req: any, res: any) => { + try { + const { subdomain, context } = req.query; + const slug = context || subdomain; + + if (!slug) { + return res.status(400).json({ error: 'subdomain or context param required' }); + } + + const community = await Community.findOne({ + where: { subdomain: slug }, + attributes: [ + 'title', + 'avatar', + 'headerLogo', + 'accentColorLight', + 'accentColorDark', + 'subdomain', + ], + }); + + if (!community) { + return res.status(404).json({ error: 'Community not found' }); + } + + return res.json({ + communityName: community.title, + logoUrl: community.avatar || community.headerLogo, + accentColorLight: community.accentColorLight, + accentColorDark: community.accentColorDark, + headerLogo: community.headerLogo, + subdomain: community.subdomain, + }); + } catch (err) { + console.error('Branding API error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── Summary API (for KF Account roster / Hub) ────────────────────── + +router.get('/api/kf/summary', requireInternalKey, async (req: any, res: any) => { + try { + const { kf_org_id } = req.query; + if (!kf_org_id) { + return res.status(400).json({ error: 'kf_org_id is required' }); + } + + const communities = await Community.findAll({ + where: { kfOrgId: kf_org_id }, + attributes: ['id', 'title', 'subdomain', 'domain', 'avatar'], + }); + + const accounts = await Promise.all( + communities.map(async (community: any) => { + const [pubCount, memberCount] = await Promise.all([ + Pub.count({ where: { communityId: community.id } }), + Member.count({ + where: { communityId: community.id }, + }), + ]); + + const host = community.domain || `${community.subdomain}.pubpub.org`; + const protocol = isProd() ? 'https' : 'http'; + + return { + id: community.id, + slug: community.subdomain, + type: 'community', + name: community.title, + url: `${protocol}://${host}`, + avatar: community.avatar || null, + stats: { pubs: pubCount, members: memberCount }, + collections: [], + }; + }), + ); + + return res.json({ accounts }); + } catch (err) { + console.error('Summary API error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── Billing usage API (placeholder) ───────────────────────────────── + +router.get('/api/kf/billing/usage', requireInternalKey, async (req: any, res: any) => { + try { + const { kf_org_id } = req.query; + if (!kf_org_id) { + return res.status(400).json({ error: 'kf_org_id is required' }); + } + + const communityCount = await Community.count({ + where: { kfOrgId: kf_org_id }, + }); + + // Placeholder — just return community count for now + return res.json({ + kf_org_id, + line_items: [ + { key: 'communities', quantity: communityCount }, + ], + }); + } catch (err) { + console.error('Billing usage API error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── User's KF orgs (session-authenticated) ───────────────────────── + +router.get('/api/kf/my-orgs', async (req: any, res: any) => { + if (!req.user?.id) { + return res.status(401).json({ error: 'Not authenticated' }); + } + try { + const orgs = await fetchUserOrgs(req.user.id); + return res.json({ orgs }); + } catch (err) { + console.error('Failed to fetch KF orgs:', err); + return res.status(500).json({ error: 'Failed to fetch organizations' }); + } +}); + +// ─── Transfer community ownership ─────────────────────────────────── + +router.post('/api/kf/transfer-community', async (req: any, res: any) => { + if (!req.user?.id) { + return res.status(401).json({ error: 'Not authenticated' }); + } + + const { communityId, kfOrgId } = req.body; + if (!communityId || !kfOrgId) { + return res.status(400).json({ error: 'communityId and kfOrgId are required' }); + } + + try { + // Verify the user is an admin of this community + await ensureUserIsCommunityAdmin({ ...req, id: communityId }); + } catch { + return res.status(403).json({ error: 'You must be an admin of this community' }); + } + + try { + // Verify the user belongs to the target org + const userOrgs = await fetchUserOrgs(req.user.id); + const targetOrg = userOrgs.find((o) => o.id === kfOrgId); + if (!targetOrg) { + return res.status(403).json({ error: 'You are not a member of the target organization' }); + } + + // Update the community's kfOrgId + const [updatedCount] = await Community.update( + { kfOrgId }, + { where: { id: communityId } }, + ); + + if (updatedCount === 0) { + return res.status(404).json({ error: 'Community not found' }); + } + + return res.json({ success: true, kfOrgId }); + } catch (err) { + console.error('Transfer community error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── Community detail (for Hubs dashboard) ─────────────────────────── + +router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, res: any) => { + try { + const communityId = req.params.id; + // Optional date range params for analytics + const startDate = req.query.startDate || null; + const endDate = req.query.endDate || null; + // Determine analytics date range + const analyticsStart = startDate || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); + const analyticsEnd = endDate || new Date().toISOString().slice(0, 10); + const pubsMonthsBack = startDate + ? Math.max(Math.ceil((Date.now() - new Date(startDate).getTime()) / (30 * 24 * 60 * 60 * 1000)), 3) + : 24; + + const community = await Community.findByPk(communityId, { + attributes: ['id', 'title', 'subdomain', 'domain', 'avatar', 'accentColorDark', 'accentColorLight', 'headerLogo', 'heroLogo', 'description', 'heroBackgroundImage', 'heroImage'], + }); + + if (!community) { + return res.status(404).json({ error: 'Community not found' }); + } + + const protocol = isProd() ? 'https' : 'http'; + const host = (community as any).domain || `${(community as any).subdomain}.pubpub.org`; + + // Run queries in parallel + const hasReleaseInclude = { + model: Release, + as: 'releases', + attributes: [], + required: true, + where: {}, + }; + + const [ + pubCount, + memberCount, + collectionCount, + releaseCount, + members, + recentPubRows, + pubsByMonthRows, + topAuthorsRaw, + collectionsRaw, + ] = await Promise.all([ + Pub.count({ where: { communityId }, include: [hasReleaseInclude] }), + Member.count({ where: { communityId } }), + Collection.count({ where: { communityId } }), + sequelize.query( + `SELECT COUNT(*)::int AS count FROM "Releases" r INNER JOIN "Pubs" p ON r."pubId" = p.id WHERE p."communityId" = :communityId`, + { replacements: { communityId }, type: 'SELECT' as any }, + ).then((rows: any) => rows[0]?.count ?? 0), + // Members with user details + Member.findAll({ + where: { communityId }, + attributes: ['id', 'userId', 'permissions', 'isOwner', 'createdAt'], + include: [{ + model: User, + as: 'user', + attributes: ['fullName', 'avatar', 'slug'], + }], + order: [['createdAt', 'ASC']], + limit: 500, + }), + // Recent pubs (released only) + Pub.findAll({ + where: { communityId }, + attributes: ['id', 'title', 'slug', 'description', 'avatar', 'customPublishedAt', 'createdAt'], + include: [ + hasReleaseInclude, + { + model: PubAttribution, + as: 'attributions', + attributes: ['name', 'avatar', 'order', 'isAuthor'], + where: { isAuthor: true }, + required: false, + include: [{ model: User, as: 'user', attributes: ['fullName', 'avatar', 'slug'] }], + }, + ], + order: [['createdAt', 'DESC']], + limit: 500, + }), + // Pubs by month + sequelize.query( + `SELECT + to_char(date_trunc('month', p."createdAt"), 'YYYY-MM') AS month, + COUNT(*)::int AS count + FROM "Pubs" p + INNER JOIN "Releases" r ON r."pubId" = p.id + WHERE p."communityId" = :communityId + AND p."createdAt" >= NOW() - INTERVAL '${pubsMonthsBack} months' + GROUP BY 1 ORDER BY 1`, + { + replacements: { communityId }, + type: 'SELECT' as any, + }, + ), + // Top authors + PubAttribution.findAll({ + attributes: ['userId', 'name', 'avatar'], + where: { isAuthor: true }, + include: [ + { + model: Pub, + as: 'pub', + attributes: [], + where: { communityId }, + required: true, + include: [hasReleaseInclude], + }, + { + model: User, + as: 'user', + attributes: ['fullName', 'avatar', 'slug'], + required: false, + }, + ], + }), + // Collections with pub counts + sequelize.query( + `SELECT + c."id", c."title", c."slug", c."kind", + COUNT(cp."pubId")::int AS "pubCount" + FROM "Collections" c + LEFT JOIN "CollectionPubs" cp ON cp."collectionId" = c."id" + WHERE c."communityId" = :communityId + GROUP BY c."id", c."title", c."slug", c."kind" + ORDER BY "pubCount" DESC`, + { replacements: { communityId }, type: 'SELECT' as any }, + ), + ]); + + // Format members + const memberList = members.map((m: any) => { + const mj = m.toJSON(); + return { + id: mj.id, + userId: mj.userId, + name: mj.user?.fullName ?? 'Unknown', + avatar: mj.user?.avatar ?? null, + slug: mj.user?.slug ?? null, + role: mj.isOwner ? 'owner' : (mj.permissions ?? 'view'), + createdAt: mj.createdAt, + }; + }); + + // Format recent pubs + const recentPubs = recentPubRows.map((p: any) => { + const pj = p.toJSON(); + const authors = (pj.attributions || []) + .sort((a: any, b: any) => (a.order || 0) - (b.order || 0)) + .map((attr: any) => ({ + name: attr.user?.fullName || attr.name || 'Unknown', + avatar: attr.user?.avatar || null, + slug: attr.user?.slug || null, + })); + return { + id: pj.id, + title: pj.title, + slug: pj.slug, + description: pj.description, + avatar: pj.avatar, + publishedAt: pj.customPublishedAt || pj.createdAt, + authors, + }; + }); + + // Aggregate top authors + const authorMap = new Map(); + for (const attr of topAuthorsRaw) { + const a = (attr as any).toJSON(); + const key = a.userId || `name:${a.name}`; + const existing = authorMap.get(key); + if (existing) { + existing.count++; + } else { + authorMap.set(key, { + name: a.user?.fullName || a.name || 'Unknown', + avatar: a.user?.avatar || a.avatar || null, + slug: a.user?.slug || null, + count: 1, + }); + } + } + const topAuthors = [...authorMap.values()] + .sort((a, b) => b.count - a.count); + + // Try to get analytics (daily views for selected range) from matview + let dailyViews: Array<{ date: string; views: number }> = []; + try { + dailyViews = (await sequelize.query( + `SELECT + date::text, + page_views::int AS views + FROM analytics_daily_summary + WHERE "communityId" = :communityId + AND date >= :analyticsStart::date + AND date <= :analyticsEnd::date + ORDER BY date`, + { + replacements: { communityId, analyticsStart, analyticsEnd }, + type: 'SELECT' as any, + }, + )) as any; + } catch { + // Matview may not exist in dev — that's fine + } + + // Total views/downloads from the selected range + let totalPageViews = 0; + let totalDownloads = 0; + try { + const [totals] = (await sequelize.query( + `SELECT + COALESCE(SUM(page_views), 0)::int AS views, + COALESCE(SUM(downloads), 0)::int AS downloads + FROM analytics_daily_summary + WHERE "communityId" = :communityId + AND date >= :analyticsStart::date + AND date <= :analyticsEnd::date`, + { + replacements: { communityId, analyticsStart, analyticsEnd }, + type: 'SELECT' as any, + }, + )) as any[]; + totalPageViews = totals?.views ?? 0; + totalDownloads = totals?.downloads ?? 0; + } catch { + // Matview may not exist + } + + return res.json({ + community: { + id: (community as any).id, + title: (community as any).title, + subdomain: (community as any).subdomain, + domain: (community as any).domain, + url: `${protocol}://${host}`, + avatar: (community as any).avatar, + headerLogo: (community as any).headerLogo, + heroLogo: (community as any).heroLogo, + description: (community as any).description, + accentColorDark: (community as any).accentColorDark, + accentColorLight: (community as any).accentColorLight, + heroBackgroundImage: (community as any).heroBackgroundImage, + heroImage: (community as any).heroImage, + }, + stats: { + pubs: pubCount, + members: memberCount, + collections: collectionCount, + releases: releaseCount, + totalPageViews, + totalDownloads, + }, + members: memberList, + recentPubs, + topAuthors, + pubsByMonth: pubsByMonthRows, + dailyViews, + collections: (collectionsRaw as any[]).map((c: any) => ({ + id: c.id, + title: c.title, + slug: c.slug, + kind: c.kind ?? 'tag', + pubCount: c.pubCount, + })), + }); + } catch (err) { + console.error('Community detail API error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── Suggested Communities (domain-based discovery) ────────────────── + +router.get('/api/kf/suggested-communities', requireInternalKey, async (req: any, res: any) => { + try { + const domainsParam = req.query.domains as string; + const excludeIds = req.query.excludeIds as string || ''; + if (!domainsParam) return res.json([]); + + const domains = domainsParam.split(',').map((d: string) => d.trim().toLowerCase()).filter(Boolean); + if (domains.length === 0) return res.json([]); + + const excludeList = excludeIds.split(',').filter(Boolean); + + // Build domain match clause for User.email + const domainClauses: string[] = []; + const replacements: Record = {}; + domains.forEach((d, i) => { + domainClauses.push( + `(LOWER(SUBSTRING("Users"."email" FROM '@(.+)$')) = :dom${i} OR LOWER(SUBSTRING("Users"."email" FROM '@(.+)$')) LIKE :domLike${i})`, + ); + replacements[`dom${i}`] = d; + replacements[`domLike${i}`] = `%.${d}`; + }); + const domainWhere = domainClauses.join(' OR '); + + // Find communities with managers matching the domains + const managersQuery = ` + SELECT "Members"."communityId", COUNT(DISTINCT "Members"."userId")::int AS "managerCount" + FROM "Members" + INNER JOIN "Users" ON "Users"."id" = "Members"."userId" + WHERE "Members"."communityId" IS NOT NULL + AND "Members"."permissions" IN ('manage', 'admin') + AND (${domainWhere}) + GROUP BY "Members"."communityId" + `; + + // Find communities with authors matching the domains + const authorsQuery = ` + SELECT "Pubs"."communityId", COUNT(DISTINCT "PubAttributions"."userId")::int AS "authorCount" + FROM "PubAttributions" + INNER JOIN "Pubs" ON "Pubs"."id" = "PubAttributions"."pubId" + INNER JOIN "Users" ON "Users"."id" = "PubAttributions"."userId" + WHERE "PubAttributions"."isAuthor" = true + AND "PubAttributions"."userId" IS NOT NULL + AND (${domainWhere}) + GROUP BY "Pubs"."communityId" + `; + + const [managerRows, authorRows] = await Promise.all([ + sequelize.query(managersQuery, { replacements, type: 'SELECT' as any }) as any, + sequelize.query(authorsQuery, { replacements, type: 'SELECT' as any }) as any, + ]); + + // Merge counts + const communityMap = new Map(); + for (const row of managerRows) { + communityMap.set(row.communityId, { managerCount: row.managerCount, authorCount: 0 }); + } + for (const row of authorRows) { + const existing = communityMap.get(row.communityId) || { managerCount: 0, authorCount: 0 }; + existing.authorCount = row.authorCount; + communityMap.set(row.communityId, existing); + } + + if (communityMap.size === 0) return res.json([]); + + // Exclude already-added communities + for (const id of excludeList) communityMap.delete(id); + if (communityMap.size === 0) return res.json([]); + + const communityIds = [...communityMap.keys()]; + const idPlaceholders = communityIds.map((_, i) => `:cid${i}`).join(', '); + const idReplacements: Record = {}; + communityIds.forEach((id, i) => { idReplacements[`cid${i}`] = id; }); + + const communityRows = (await sequelize.query( + `SELECT "id", "title", "subdomain", "domain", "description", "heroLogo", "accentColorDark", "accentColorLight" + FROM "Communities" + WHERE "id" IN (${idPlaceholders}) + ORDER BY "title" ASC`, + { replacements: idReplacements, type: 'SELECT' as any }, + )) as any[]; + + const results = communityRows.map((c: any) => { + const counts = communityMap.get(c.id) || { managerCount: 0, authorCount: 0 }; + return { + id: c.id, + title: c.title, + subdomain: c.subdomain, + domain: c.domain, + description: c.description, + heroLogo: c.heroLogo, + accentColorDark: c.accentColorDark, + accentColorLight: c.accentColorLight, + managerCount: counts.managerCount, + authorCount: counts.authorCount, + }; + }); + + return res.json(results); + } catch (err) { + console.error('Suggested communities API error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── Suggested Pubs (full-text search discovery) ───────────────────── + +router.get('/api/kf/suggested-pubs', requireInternalKey, async (req: any, res: any) => { + try { + const termsParam = req.query.terms as string; + const excludeCommunityIds = req.query.excludeCommunityIds as string || ''; + const limit = Math.min(parseInt(req.query.limit as string, 10) || 50, 200); + if (!termsParam) return res.json([]); + + const terms = termsParam.split(',').map((t: string) => t.trim()).filter(Boolean); + if (terms.length === 0) return res.json([]); + + const excludeList = excludeCommunityIds.split(',').filter(Boolean); + + // Build tsquery from terms (OR them together) + const tsQuery = terms.map((t) => t.split(/\s+/).join(' & ')).join(' | '); + + let excludeClause = ''; + const replacements: Record = { tsQuery, limit }; + if (excludeList.length > 0) { + const excludePlaceholders = excludeList.map((_, i) => `:excl${i}`).join(', '); + excludeList.forEach((id, i) => { replacements[`excl${i}`] = id; }); + excludeClause = `AND p."communityId" NOT IN (${excludePlaceholders})`; + } + + const rows = (await sequelize.query( + `SELECT + p."id", + p."title", + p."slug", + p."description", + p."avatar", + p."communityId", + c."title" AS "communityTitle", + c."subdomain" AS "communitySubdomain", + c."domain" AS "communityDomain", + ts_rank(p."searchVector", to_tsquery('english', :tsQuery)) AS "rank" + FROM "Pubs" p + INNER JOIN "Communities" c ON c."id" = p."communityId" + INNER JOIN "Releases" r ON r."pubId" = p."id" + WHERE p."searchVector" @@ to_tsquery('english', :tsQuery) + ${excludeClause} + GROUP BY p."id", c."id" + ORDER BY "rank" DESC + LIMIT :limit`, + { replacements, type: 'SELECT' as any }, + )) as any[]; + + return res.json(rows.map((r: any) => ({ + id: r.id, + title: r.title, + slug: r.slug, + description: r.description, + avatar: r.avatar, + communityId: r.communityId, + communityTitle: r.communityTitle, + communitySubdomain: r.communitySubdomain, + communityDomain: r.communityDomain, + rank: parseFloat(r.rank), + }))); + } catch (err) { + console.error('Suggested pubs API error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + +// ─── Graph data (cross-community people network) ──────────────────── + +router.get('/api/kf/graph-data', requireInternalKey, async (req: any, res: any) => { + try { + const communityIdsParam = req.query.communityIds as string; + if (!communityIdsParam) return res.json({ nodes: [], links: [] }); + + const communityIds = communityIdsParam.split(',').filter(Boolean); + if (communityIds.length === 0) return res.json({ nodes: [], links: [] }); + + const idPlaceholders = communityIds.map((_: string, i: number) => `:cid${i}`).join(', '); + const replacements: Record = {}; + communityIds.forEach((id: string, i: number) => { replacements[`cid${i}`] = id; }); + + // Get communities + const communities = (await sequelize.query( + `SELECT "id", "title", "subdomain", "accentColorDark" + FROM "Communities" + WHERE "id" IN (${idPlaceholders})`, + { replacements, type: 'SELECT' as any }, + )) as any[]; + + // Get people who appear in multiple communities (managers + authors) + const peopleQuery = ` + SELECT + u."id" AS "userId", + u."fullName" AS "name", + u."avatar", + array_agg(DISTINCT sub."communityId") AS "communityIds", + array_agg(DISTINCT sub."role") AS "roles" + FROM ( + SELECT m."userId", m."communityId", 'member' AS "role" + FROM "Members" m + WHERE m."communityId" IN (${idPlaceholders}) + AND m."userId" IS NOT NULL + UNION ALL + SELECT pa."userId", p."communityId", 'author' AS "role" + FROM "PubAttributions" pa + INNER JOIN "Pubs" p ON p."id" = pa."pubId" + WHERE p."communityId" IN (${idPlaceholders}) + AND pa."userId" IS NOT NULL + AND pa."isAuthor" = true + ) sub + INNER JOIN "Users" u ON u."id" = sub."userId" + GROUP BY u."id", u."fullName", u."avatar" + HAVING COUNT(DISTINCT sub."communityId") >= 2 + ORDER BY COUNT(DISTINCT sub."communityId") DESC + LIMIT 200 + `; + + const people = (await sequelize.query(peopleQuery, { + replacements, + type: 'SELECT' as any, + })) as any[]; + + // Build graph nodes and links + type GraphNode = { id: string; label: string; type: 'community' | 'person'; color?: string; avatar?: string }; + type GraphLink = { source: string; target: string; roles: string[] }; + + const nodes: GraphNode[] = [ + ...communities.map((c: any) => ({ + id: c.id, + label: c.title, + type: 'community' as const, + color: c.accentColorDark ?? '#5c7080', + })), + ...people.map((p: any) => ({ + id: p.userId, + label: p.name ?? 'Anonymous', + type: 'person' as const, + avatar: p.avatar, + })), + ]; + + const links: GraphLink[] = []; + for (const p of people) { + for (const cid of p.communityIds) { + if (communityIds.includes(cid)) { + links.push({ + source: p.userId, + target: cid, + roles: p.roles ?? [], + }); + } + } + } + + return res.json({ nodes, links, communities: communities.length, people: people.length }); + } catch (err) { + console.error('Graph data API error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); diff --git a/server/kf/auth.ts b/server/kf/auth.ts new file mode 100644 index 000000000..00d91c57d --- /dev/null +++ b/server/kf/auth.ts @@ -0,0 +1,150 @@ +/** + * Lightweight OIDC client for KF Auth (PubPub edition). + * + * Two base URLs: + * KF_AUTH_INTERNAL_URL — server-to-server (e.g. kf-auth:3000 on Hetzner internal network) + * KF_AUTH_URL — browser-facing (e.g. https://auth.knowledgefutures.org) + */ + +import crypto from 'node:crypto'; + +/** Browser-facing URL for auth redirects. */ +const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000'; +/** Server-side URL for token exchange / userinfo. Falls back to KF_AUTH_URL. */ +const KF_AUTH_INTERNAL_URL = process.env.KF_AUTH_INTERNAL_URL ?? KF_AUTH_URL; +const KF_AUTH_CLIENT_ID = process.env.KF_AUTH_CLIENT_ID ?? 'kf_pubpub'; +const KF_AUTH_CLIENT_SECRET = process.env.KF_AUTH_CLIENT_SECRET ?? ''; +const APP_URL = process.env.APP_URL ?? 'http://localhost:9876'; +const REDIRECT_URI = `${APP_URL}/auth/callback`; + +// BetterAuth OIDC endpoints +const AUTHORIZE_PATH = '/api/auth/oauth2/authorize'; +const TOKEN_PATH = '/api/auth/oauth2/token'; +const USERINFO_PATH = '/api/auth/oauth2/userinfo'; + +// ── PKCE helpers ───────────────────────────────────────────────────── + +export function generateCodeVerifier(): string { + return crypto.randomBytes(32).toString('base64url'); +} + +export function generateCodeChallenge(verifier: string): string { + return crypto.createHash('sha256').update(verifier).digest('base64url'); +} + +// ── Authorize URL ──────────────────────────────────────────────────── + +/** + * Build the URL to redirect the user to for authentication. + * `state` should include the community subdomain/domain for post-login redirect. + */ +export function buildAuthorizeUrl(state: string): { + url: string; + codeVerifier: string; +} { + const codeVerifier = generateCodeVerifier(); + const codeChallenge = generateCodeChallenge(codeVerifier); + const params = new URLSearchParams({ + client_id: KF_AUTH_CLIENT_ID, + redirect_uri: REDIRECT_URI, + response_type: 'code', + scope: 'openid profile email', + state, + code_challenge: codeChallenge, + code_challenge_method: 'S256', + }); + return { url: `${KF_AUTH_URL}${AUTHORIZE_PATH}?${params}`, codeVerifier }; +} + +// ── Token exchange ─────────────────────────────────────────────────── + +interface TokenResponse { + access_token: string; + token_type: string; + expires_in: number; + id_token?: string; + refresh_token?: string; +} + +export async function exchangeCode( + code: string, + codeVerifier: string, +): Promise { + const body = new URLSearchParams({ + grant_type: 'authorization_code', + code, + redirect_uri: REDIRECT_URI, + client_id: KF_AUTH_CLIENT_ID, + client_secret: KF_AUTH_CLIENT_SECRET, + code_verifier: codeVerifier, + }); + + const res = await fetch(`${KF_AUTH_INTERNAL_URL}${TOKEN_PATH}`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body, + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`Token exchange failed: ${res.status} ${text}`); + } + + return res.json() as Promise; +} + +// ── UserInfo ───────────────────────────────────────────────────────── + +export interface KFOrg { + id: string; + name: string; + slug: string; + type: 'personal' | 'shared'; + role: string; +} + +export interface KFUserInfo { + sub: string; + name?: string; + email?: string; + picture?: string; + given_name?: string; + family_name?: string; + 'https://knowledgefutures.org/orgs'?: KFOrg[]; +} + +export async function fetchUserInfo(accessToken: string): Promise { + const res = await fetch(`${KF_AUTH_INTERNAL_URL}${USERINFO_PATH}`, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + if (!res.ok) { + throw new Error(`UserInfo failed: ${res.status}`); + } + + return res.json() as Promise; +} + +/** + * Fetch a user's current KF orgs from KF Auth's internal API. + * Used for the ownership picker when creating communities. + */ +export async function fetchUserOrgs( + userId: string, +): Promise { + const key = process.env.KF_INTERNAL_API_KEY; + if (!key) return []; + + const res = await fetch( + `${KF_AUTH_INTERNAL_URL}/api/internal/users/${userId}/orgs`, + { + headers: { Authorization: `Bearer ${key}` }, + }, + ); + + if (!res.ok) return []; + const data = (await res.json()) as { orgs?: KFOrg[] }; + return data.orgs ?? []; +} + +export { KF_AUTH_URL, KF_AUTH_CLIENT_ID, APP_URL, REDIRECT_URI }; diff --git a/server/routes/communityCreate.tsx b/server/routes/communityCreate.tsx index ab3653f50..cf2d9ba8f 100644 --- a/server/routes/communityCreate.tsx +++ b/server/routes/communityCreate.tsx @@ -10,6 +10,7 @@ import { getHubWithCommunities, isUserHubManager, } from 'server/hub/queries'; +import { fetchUserOrgs } from 'server/kf/auth'; import { handleErrors } from 'server/utils/errors'; import { getInitialData } from 'server/utils/initData'; import { hostIsValid } from 'server/utils/routes'; @@ -27,8 +28,9 @@ router.get('/community/create', (req, res, next) => { return Promise.all([ getInitialData(req), hubSlug ? getHubBySlug(hubSlug) : Promise.resolve(null), + req.user?.id ? fetchUserOrgs(req.user.id) : Promise.resolve([]), ]) - .then(async ([initialData, hubData]) => { + .then(async ([initialData, hubData, kfOrgs]) => { const templates = hubData ? await getActiveTemplatesForHub(hubData.id) : []; // Fetch hub communities for the clone-from-community picker @@ -81,7 +83,7 @@ router.get('/community/create', (req, res, next) => { { + const returnTo = req.query.redirect || req.query.return_to || '/'; + return res.redirect(`/auth/login?return_to=${encodeURIComponent(String(returnTo))}`); +}); diff --git a/server/routes/passwordReset.kf.tsx b/server/routes/passwordReset.kf.tsx new file mode 100644 index 000000000..6e4e4437e --- /dev/null +++ b/server/routes/passwordReset.kf.tsx @@ -0,0 +1,17 @@ +/** + * Phase C: Password reset redirect. + * + * Password management now happens through KF Auth. + * Redirect both the request-reset page and the reset-with-hash page. + */ + +import { Router } from 'express'; + +import { KF_AUTH_URL } from 'server/kf/auth'; + +export const router = Router(); + +router.get(['/password-reset', '/password-reset/:resetHash/:slug'], (req, res) => { + // Old reset links won't work; redirect to KF Auth's password reset flow + return res.redirect(`${KF_AUTH_URL}/forgot-password`); +}); diff --git a/server/routes/signup.kf.tsx b/server/routes/signup.kf.tsx new file mode 100644 index 000000000..ac7f59e06 --- /dev/null +++ b/server/routes/signup.kf.tsx @@ -0,0 +1,29 @@ +/** + * Phase C: Signup page redirect. + * + * Instead of rendering the PubPub signup page, redirect to KF Auth's + * sign-up flow. KF Auth handles account creation now. + */ + +import { Router } from 'express'; + +import { KF_AUTH_URL, KF_AUTH_CLIENT_ID, APP_URL } from 'server/kf/auth'; + +export const router = Router(); + +router.get('/signup', (req, res) => { + // Redirect to KF Auth's sign-up page, passing the PubPub client_id + // so KF Auth shows PubPub-branded signup and redirects back after. + const params = new URLSearchParams({ + client_id: KF_AUTH_CLIENT_ID, + redirect_uri: `${APP_URL}/auth/callback`, + }); + return res.redirect(`${KF_AUTH_URL}/sign-up?${params}`); +}); + +// Also redirect the /user/create/:hash route (email verification step) +// These links in old verification emails won't work after migration; +// users who click them should be directed to sign up fresh via KF Auth. +router.get('/user/create/:hash', (req, res) => { + return res.redirect(`${KF_AUTH_URL}/sign-up`); +}); diff --git a/tools/migrations/2026_05_15_addKfOrgIdToCommunities.js b/tools/migrations/2026_05_15_addKfOrgIdToCommunities.js new file mode 100644 index 000000000..6b2e95e71 --- /dev/null +++ b/tools/migrations/2026_05_15_addKfOrgIdToCommunities.js @@ -0,0 +1,17 @@ +export const up = async ({ Sequelize, sequelize }) => { + await sequelize.queryInterface.addColumn('Communities', 'kfOrgId', { + type: Sequelize.TEXT, + allowNull: true, + }); + await sequelize.queryInterface.addIndex('Communities', ['kfOrgId'], { + name: 'communities_kf_org_id_idx', + }); +}; + +export const down = async ({ sequelize }) => { + await sequelize.queryInterface.removeIndex( + 'Communities', + 'communities_kf_org_id_idx', + ); + await sequelize.queryInterface.removeColumn('Communities', 'kfOrgId'); +}; diff --git a/tools/migrations/2026_06_15_makeKfOrgIdNotNull.js b/tools/migrations/2026_06_15_makeKfOrgIdNotNull.js new file mode 100644 index 000000000..f9ec0a880 --- /dev/null +++ b/tools/migrations/2026_06_15_makeKfOrgIdNotNull.js @@ -0,0 +1,33 @@ +/** + * Phase D cleanup: Make kfOrgId NOT NULL on Communities. + * + * Run this ONLY after confirming all communities have been assigned + * a kfOrgId value (from the seed script + new community creation). + */ + +export const up = async ({ Sequelize, sequelize }) => { + // First verify there are no NULL values + const [results] = await sequelize.query( + `SELECT count(*) as count FROM "Communities" WHERE "kfOrgId" IS NULL`, + ); + const nullCount = parseInt(results[0].count, 10); + + if (nullCount > 0) { + throw new Error( + `Cannot make kfOrgId NOT NULL: ${nullCount} communities still have NULL kfOrgId. ` + + `Assign ownership first, then re-run this migration.`, + ); + } + + await sequelize.queryInterface.changeColumn('Communities', 'kfOrgId', { + type: Sequelize.TEXT, + allowNull: false, + }); +}; + +export const down = async ({ Sequelize, sequelize }) => { + await sequelize.queryInterface.changeColumn('Communities', 'kfOrgId', { + type: Sequelize.TEXT, + allowNull: true, + }); +}; diff --git a/tools/migrations/2026_06_15_removePasswordColumns.js b/tools/migrations/2026_06_15_removePasswordColumns.js new file mode 100644 index 000000000..2e9d602c8 --- /dev/null +++ b/tools/migrations/2026_06_15_removePasswordColumns.js @@ -0,0 +1,35 @@ +/** + * Phase D cleanup: Remove password-related columns from Users table. + * + * After the 30-day transition period, all users authenticate via KF Auth. + * These columns are no longer needed in PubPub's database. + * + * Run this ONLY after confirming all old sessions have expired and + * the OIDC login flow is working reliably. + */ + +export const up = async ({ Sequelize, sequelize }) => { + const qi = sequelize.queryInterface; + + // Remove password-related columns + await qi.removeColumn('Users', 'hash'); + await qi.removeColumn('Users', 'salt'); + await qi.removeColumn('Users', 'passwordDigest'); + await qi.removeColumn('Users', 'sha3hashedPassword'); + await qi.removeColumn('Users', 'resetHash'); + await qi.removeColumn('Users', 'resetHashExpiration'); +}; + +export const down = async ({ Sequelize, sequelize }) => { + const qi = sequelize.queryInterface; + + // Restore password-related columns (data is gone though) + await qi.addColumn('Users', 'hash', { type: Sequelize.TEXT }); + await qi.addColumn('Users', 'salt', { type: Sequelize.TEXT }); + await qi.addColumn('Users', 'passwordDigest', { type: Sequelize.TEXT }); + await qi.addColumn('Users', 'sha3hashedPassword', { type: Sequelize.TEXT }); + await qi.addColumn('Users', 'resetHash', { type: Sequelize.TEXT }); + await qi.addColumn('Users', 'resetHashExpiration', { + type: Sequelize.DATE, + }); +}; diff --git a/utils/api/schemas/community.ts b/utils/api/schemas/community.ts index 9cc73c780..528b0b879 100644 --- a/utils/api/schemas/community.ts +++ b/utils/api/schemas/community.ts @@ -98,6 +98,7 @@ export const communitySchema = baseSchema.extend({ spamTagId: z.string().uuid().nullable(), scopeSummaryId: z.string().uuid().nullable(), templateId: z.string().uuid().nullable(), + kfOrgId: z.string().nullable(), accentTextColor: z.string(), analyticsSettings: analyticsSettingsSchema, }) satisfies z.ZodType; @@ -125,6 +126,7 @@ export const communityCreateSchema = communitySchema altcha: z.string().optional(), _honeypot: z.string().optional(), templateId: z.string().uuid().nullish(), + kfOrgId: z.string().nullish(), }); export const communityUpdateSchema = communitySchema From dcf6dcd4b9cb492b003791c863d96d962baa6c25 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sat, 16 May 2026 01:14:51 -0400 Subject: [PATCH 02/16] Send over more assets --- server/kf/api.ts | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index 8be970531..505a8d66d 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -799,10 +799,11 @@ router.get('/api/kf/suggested-communities', requireInternalKey, async (req: any, communityIds.forEach((id, i) => { idReplacements[`cid${i}`] = id; }); const communityRows = (await sequelize.query( - `SELECT "id", "title", "subdomain", "domain", "description", "heroLogo", "accentColorDark", "accentColorLight" - FROM "Communities" - WHERE "id" IN (${idPlaceholders}) - ORDER BY "title" ASC`, + `SELECT c."id", c."title", c."subdomain", c."domain", c."description", c."heroLogo", c."accentColorDark", c."accentColorLight", c."createdAt", + (SELECT COUNT(*)::int FROM "Pubs" p INNER JOIN "Releases" r ON r."pubId" = p."id" WHERE p."communityId" = c."id") AS "pubCount" + FROM "Communities" c + WHERE c."id" IN (${idPlaceholders}) + ORDER BY c."title" ASC`, { replacements: idReplacements, type: 'SELECT' as any }, )) as any[]; @@ -817,6 +818,8 @@ router.get('/api/kf/suggested-communities', requireInternalKey, async (req: any, heroLogo: c.heroLogo, accentColorDark: c.accentColorDark, accentColorLight: c.accentColorLight, + createdAt: c.createdAt, + pubCount: c.pubCount ?? 0, managerCount: counts.managerCount, authorCount: counts.authorCount, }; @@ -843,8 +846,16 @@ router.get('/api/kf/suggested-pubs', requireInternalKey, async (req: any, res: a const excludeList = excludeCommunityIds.split(',').filter(Boolean); - // Build tsquery from terms (OR them together) - const tsQuery = terms.map((t) => t.split(/\s+/).join(' & ')).join(' | '); + // Build tsquery from terms — use adjacency operator (<->) for exact phrase matching + // e.g. "Mellon Foundation" → "mellon <-> foundation", single words get prefix match + const tsQuery = terms.map((t) => { + const words = t.trim().toLowerCase().replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim().split(/\s+/).filter(Boolean); + if (words.length === 0) return null; + if (words.length === 1) return `${words[0]}:*`; + return `(${words.join(' <-> ')})`; + }).filter(Boolean).join(' | '); + + if (!tsQuery) return res.json([]); let excludeClause = ''; const replacements: Record = { tsQuery, limit }; @@ -861,11 +872,22 @@ router.get('/api/kf/suggested-pubs', requireInternalKey, async (req: any, res: a p."slug", p."description", p."avatar", + p."customPublishedAt", p."communityId", c."title" AS "communityTitle", c."subdomain" AS "communitySubdomain", c."domain" AS "communityDomain", - ts_rank(p."searchVector", to_tsquery('english', :tsQuery)) AS "rank" + ts_rank(p."searchVector", to_tsquery('english', :tsQuery)) AS "rank", + CASE WHEN p."description" IS NOT NULL AND p."description" != '' + THEN ts_headline('english', p."description", to_tsquery('english', :tsQuery), 'StartSel=,StopSel=,MaxWords=60,MinWords=20,MaxFragments=2,FragmentDelimiter= … ') + ELSE NULL + END AS "snippet", + ( + SELECT string_agg(COALESCE(u2."fullName", pa2."name"), ', ' ORDER BY pa2."order" ASC) + FROM "PubAttributions" pa2 + LEFT JOIN "Users" u2 ON u2."id" = pa2."userId" + WHERE pa2."pubId" = p."id" AND pa2."isAuthor" = true + ) AS "byline" FROM "Pubs" p INNER JOIN "Communities" c ON c."id" = p."communityId" INNER JOIN "Releases" r ON r."pubId" = p."id" @@ -887,6 +909,9 @@ router.get('/api/kf/suggested-pubs', requireInternalKey, async (req: any, res: a communityTitle: r.communityTitle, communitySubdomain: r.communitySubdomain, communityDomain: r.communityDomain, + byline: r.byline ?? null, + snippet: r.snippet ?? null, + publishedAt: r.customPublishedAt ?? null, rank: parseFloat(r.rank), }))); } catch (err) { From 20c1907662b614240ea85e81efeed6713825edf1 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sat, 16 May 2026 22:35:01 -0400 Subject: [PATCH 03/16] Update for kf auth deploy --- .gitignore | 1 + infra/.env.dev.enc | 96 +++++++++++++++++++----------------- infra/.env.local.enc | 59 ++++++++++++++++++++++ infra/.sops.yaml | 2 +- infra/docker-compose.dev.yml | 4 +- package.json | 2 + scripts/confirm-encrypt.sh | 4 ++ server/kf/api.ts | 16 +++--- server/kf/auth.ts | 14 ++---- 9 files changed, 135 insertions(+), 63 deletions(-) create mode 100644 infra/.env.local.enc diff --git a/.gitignore b/.gitignore index 48735d7ba..0069a25b7 100755 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ yarn-error.log* .env.* !.env.enc !.env.dev.enc +!.env.local.enc !infra/.env.test diff --git a/infra/.env.dev.enc b/infra/.env.dev.enc index 6db5bf1c2..eb0c10569 100644 --- a/infra/.env.dev.enc +++ b/infra/.env.dev.enc @@ -1,53 +1,59 @@ -AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:YwdwuazMHhri6N2Lxs4mNbm5hwRSJm3yop5LxmzaB/6f36IbuVcPL9bZEpUhqSbbza0Uw1FYAzimLF5PMfor2Q==,iv:CImEY++dwcJjJ6shRdnhEgwFUwvBRvWL5XejfKZJVB4=,tag:wlbsUyJlvVCPpkDWwuaB8g==,type:str] -ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:m3h3mTBUsQQVKP9vqfHzgB3yLPNSuAMX8von2pALFWLl11sw5oeuyetx0oznddZCQjE596h7WAo0KN6sQJslmw==,iv:Mcojr/cgUKz4DQuNESe/1tDnlVeUaz42EhxxCOKOdiE=,tag:hsPq+qm9WqczCBR5zVFXlA==,type:str] -AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:V2EPX34kkLSzVkapq/AXTANt0RA=,iv:Vd2Hi6LxL7FXu4NdkQPvQUQHhJJecqay4IN955eTxEM=,tag:fpW+1Z1AkfqbvhasRmKD2Q==,type:str] -AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:9vHS7UaaLe4qzIMv4+/TVj/iNiU=,iv:RhGRvIoWNWsh00Eo95b14gKBUCWhL/qK+kaWWXYKIG0=,tag:PSse94gE1uwo/s+v8WojWw==,type:str] -AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:15It93JTjRwIeF+K3iC9C3fbb7oec44it5ZaQ6SZNS3BJoQG705sSQ==,iv:ur3MbUSWqnh7z9O5Ow1UbcVHLM3sQgRVucz7KDjvMz4=,tag:yWPCfshCsQRtB+7cOO8INg==,type:str] -AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:nLVLV6QiLG6DLhG/+Gqo5DmprPCNlubKUXw+1v9DXHD3AI/qnDJ4pA==,iv:meLdta+HFrOZuC7T1FHC7yAvK1ZPJqqDQ3gRigEPnvA=,tag:Gvl7KH+IwN5sDoLFl0MuKw==,type:str] -BACKUPS_SECRET=ENC[AES256_GCM,data:5ey56vyaaaFDg0yAau+rdD6DIp2ybUbsd19KkP04icjMWtYq3odwPgBYYt4=,iv:rYABukoHrmO2Q7XIMryVADsDf1li/R0R98Ct9rDtpfU=,tag:Va1tZW37BUqEQXl5Odx6cQ==,type:str] -CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:JKYGVdcx+fzXXIP/ir32J6sDbPXsUQJCs0cH8QWFOAMkHd0pORIWHtRoAbNCc+asWZKh6ts=,iv:8DIZcel5/3crIrEdmh4Acvme+K6xdXDT7J/VPqJ+IpY=,tag:3ptWLOiCtOvckryH6RaWDw==,type:str] -CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:ZqLdXUQ0kgruGjEJuF2tZ8UoqzGW5yVATq94JZy6zokILEQ9claOL06u/Kug2uXmfknUjys=,iv:rO0avhy+tZfwdOazdkNFzU0Dlno6c5M+Vhx8EUlA4rI=,tag:U3kr2EzLaJeaT/cPS72JwA==,type:str] -CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:qyoMHUDQLyTQxilvK//0sjA/UMlqC1AHE9laplkY7dc=,iv:xvM3bWtP73b+qre2Yn2ECQjle4Wz/wS/CZ3NGmehxEk=,tag:Quf9aeUIIQi7OzQwBChLEA==,type:str] -CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data:QCEEPtjLJcg4j1k9ejSIYChe45azTssuQLpjkxd27yJ0ZcDRoPR0CdaOeEHM13hxseuLj+YAcTTCB03dNo7J/8goEuAPGp7EYzJtBXyuNm9+jAZuyktG/5+KCxzQ58KSQRUsPEYvj8G2MDdQqQm4/159L00a9jfitX/jUlA0ouLBk1Hk40ndkzISOLn8lh6k7El1dTKXp6GkaazOFQ3zeMw7pwk/Nxo/8qiA3uMgYZGAJH7IPk7YbhkfiDYRfRUtfkFMfHh3R0qAPeMyBQFJMqY5yvdX1UybsBlVhkOvZ6xeDEnXI8pHtAxquwzCuWvCJjh0tAinH4xZ4MphcHc3ljkHV+esvoafqMwNUE3na/4eZTQ1G5c9VxNRoffxwur10zO76MOtWPqRbSw6y1F2ZMKLeR/eUUkWOt9gHk7xiAag/E5rk3KAKfLQD05v64pEhcsXcpUsT6v87rP/1UJuKkx6DEl8iErR9c0W5OpUt/h+ZaN9qgQLiSfr66byQUMOLL2OVlVa7brUnFhriGJ11+nK4OE77zk7r4BT8Nb4gzE0FPVMfqFJkf8Su9I3GzUYD+LeW4BFbZjz3BzKiqGYtKrtO0S5nJBCpZjpj4aEilfLakh+SNyLZrV3Y7YtgL+yXanm5yjXYxtNmuxR9jg8XYfFpEd5qYvyXs27rosaA0TgKEbbSK+ofCB+3KmUITawLeGfUEhyTZFINBEeBC9I81Uhb25uDgdCXHenfdM5BhSbECk96tDAVcGxGX6xR4CH9cBgztMq3lAf3SuS9ZZqOyh/bc/deggo2loxiAPVxs35HLmNa3BIjtwe/HBIKP1HJSSovtk6J7OX+QOCY6mQRevPBsYY61zFFu5eDmul9Ri4kN/XkCkCUGezZ8W0bh+ekPdHNOyQgkdv/8e092qE2a9oIebRMPXuPv6TrUTBd6y6AKns7Zu+1spWUGU/nrEltRO+Xu+y8rDiKrG7VQZ6jHtQLtnI6U+NGf+jNirrhM8jr/trfIusdGoRUbVTqrLJfCe4jCA2cF5WoBUJzsiIQ338m2euK1aq014bWmvLWqN0QC0tE18fqpIen4+EPB9uzIVtzrGENNHsORYAIJpS5xaKHI2lJBxqO13INTwYoEjbMs5OwkG+fuaib0mVeo0LUEo/OZ9ID2ds7f5eMjOr76wkBVFbZNgTn3q+v98zhMSP4kcQlCZRo9LC36BgOONmmn2j/kfdr6u3a4/Khx1qC7Uf6HfJkb0LZayHj6IWH3NJLlq7kbHD0xVpMG+vbHHoOUBrLr03KDXIoqUL04FhbE9pc09/JnxQSwX/AH22yM0kWGK4ZBLLDqn+yYW3TUhwBFV5vWQug/kXUbSXH933r3UYO7ykPk7g9DTaB5F6FiKQeVlR89YBdugcOz6oY/QFAHQOFehDpBmoPEK5Mjuy6Quv4CHS0yc1E0/v+AYtuLulNAnqkQqRrceXhQe3o9dk2E0XL6zffgBFDGVeUm9t+R/g5cOWx+qr3lzhvd5wJnAXTrixXmig6JPdqlsCfRMapymAZgKMTm0Zw2W/3JiSwBxdHwUqRWcz+IiKLXdY3FVSJmojQn5OKyVJl919BzZMk6maT4gHI8xAbEyREGbeqrb8A08KfdXoEz6nhG92Yt/I69kClTxnyOeFhT3nwu+HGGuPVkOURJlXtcaQ3jZJ2zXEy9+39TSNO0jSWAFFvHdIrZwsc5sUfeqP930xAnH6TbN1YxoRlfO4Jt6HBLKASRoiVqCoswmcPrVZ4N28Dr46PpS+IgxCg9862AXDGnDIytokgSU1uPGODiKwNyd0FPiLpBrvcTc1I3Ja0C15Ivw+PQIbyk5kz7+9/1fzSsVM7bZ1tA6jlQ9SAzMtQLeql9nMm4QBKk44hPVoVlDwrH8MjLA37cbNQBjawrYMEuqtnsd4qeoyvMb30ZOQBjs+ipwzCQnQOYGOvb08nvrAtdOy4tMCNtHPnSS2Yqxyt7XfBNoz1jsD7G8GsvH7w882EgoWwaLXsByqYHQsAQABt6G3+/CBEvnWhGoqFI7pN3OqRLyIT1SB91/pN2BQoI2BoU4+eBT340w0Fmlgp4yqVHBycgAfekj2RZdpD7jahW2LRcSbiGm3geBj5Kv1fpMi+j1LLp23ASR1BmICZL9E76uPT96kbxueam/ICcieH5pSTm4DuqSxSFz8LEv0kQ7i7+JH+7b+3nXqN/O7fhWUoxdZNCgD0AMTOJub+d2vi+FBsBcUfiQTLfGfAzhyLDc3Aws++I6w2ct1f2BvPaL+shZuvOnlbesYF97bcoL2jcTb/2M+O8dvtAuTRij8PaapaCcmWEhSrA1iUenSzLsCOLz7f7g+oUa23fayWY+1RH9xOjlMQwCQIh0EcU0aSQIInR2Z3wk/YOG+/yKQKYpyK5sTlTX+c7AGxttBC33g2Z3A3nbiqRJl0Hy4UU9NpnXacvPZpYCOuwNb95kmpGomck62n1GvIa6t3ImS3O22gvw65qqNR51eWtvmXS9WEmhdDX4MaFg8lvLJRSSRY5sT3ae5DSSFM45d8x36eh2z5tfqJZMGh5LkgAVJ92nXz8xCQV/XezIT9uStNWS4+m1Liwkr0NiiM+8caPAZSZfcWStasr39Ie8A+n4yLaqvcLVTmKA56CM34lnx2k3nYmvNRtpzd32AGSa89tu6cy2lYWubgzuC5JDZ4U6mM7fkq4GdYGFWZG2Zx3rFTpMPaNhOe0CsYN9xqTOnPwAMWEtxCxiu2nAfLqVewyJfKYq70R/OQa9sMj3fxUdBTwkMNLCTCLvO0h1RJGEw94UhjqmyY7pGbtMDeLoWbB+nVjTWprBRNWmT+VFUdL8Uj5y10SBU7BQ+N4MPXSKPG6GwreVkeL/KQvah6SI+6SGB0N9P2VvF0SesoE8ObSxk/VxbH3SZna3cnRfRCG05cQWELDSth52cR4jIeg54iNA9tM+IlR63acEmUGgOOn+uqDllOFEWK2l53lfjyhnduKshBKrA20BUCjkQ/zxLr7s1bi0UTD48aCNbdKzBMHYwxgFE4oLyuaV1+BQ78eTvhaZw+/USqd2JpyS8vp/osQgTmkHbTY7VK7jwMjxkEcOJ/WSkydkhJubQgOZ1a/0yFbuKYtR16mNjH43UzYMaw0VmJSVXwGgs9631W+Q/qNmPGOo4HsBbVVwy+f1Mo4hNoRQ5JqA/fhjkmWeiYDzClGiHbi4m8BUP71YPcQ5/DxwVWjDzJisQemuBY/ebjoioqaYnHR2Rp7tnuMH8tjSRSILV1P6iKYE9zBpKEnG+QjFXH0BZD4Y/FSCNZMcqDiJ0TwAdsC1MHvFd+cqV95i262YuZvuE/r0uRVs+EWj6WcEau2XW5bMoQQDMxL5D3b/5My9BgDt6JNpS4mFKY1P2YDTeOJP8WDlR+NoIrmZxTBbrvgnr6WAyo5YuE7ZPkcuQuDpevFjG0YIB4NHuPDZRcbZUwxwhdDQFHxK8VVYQX/n2aYh51gNEBS7UPpb8ut4djnSxz3+QVLlW0eyKwnaCEs6br1cPTRBx8qrkYNfWmRtjs+yliit37xxL4eDwITTpNHwH0MPcqVkUARqW6qPz1EWp8DLX37eY26sXsKWXlqd2PjNrY0k++5jEaesh/Vu2kn+SX6eYwaHwsqTT5CCOomjRtduKpvvxC9KL547tPebDcgY6bI71Msj76B0V4za9GUiONdV/Ez0NMOK4eIMgVg1k6b1fh2ZHbkXm2+6aHfupevENHsCj86ijbPzWhSuHp59qmZoidJyebC4TCFWPWhBJQBr3Rlu5fJTO/x6qcFC7xaFALFpuV46MpVM0mRRH6L6ef3bK+auJWVxDhdVeCBdLMnq9ZtwB7XfyUEeZP6ZLKBe12wC45BMlMNHWYSkBb0yFvhJPtejlyvkP+eR1D5Zgjps9kFD/pMxcP8A9DlE4vbKEL6WzClDO7kXWFWGS8BESVlZQPH5jD4PsVbKc7ZzP/mN2tKQQnjSjs7E621dZ2Vckd41cv6CagSFT7/2VI5uW6XlhA6ZRB6bgVd9z0Zjq2e1ZvQjAhH/5J1TLago=,iv:ToxQVOKx3ZP+oYt0596Xa3JacoF/9qB3q4TjiUycp/g=,tag:2uQZ/E/ZytYDWidA7ATlXg==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:zaYEq9/FoXQqIjXAH1Xm2KZhTs7hxCrxaT6geoxQEJoEhgX9pVxTzlC4zbI=,iv:Zfo6DSvryrww6u+QPhAiisQsMeFKNwlDPeQE+EyHSEo=,tag:Gm1j50yWe9nD1enIV2NAxw==,type:str] -DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:O61IgJI1xGiPCxmRPDkMsMlgB9ZAxZC2Yxohb7W72DCZ3A==,iv:FaFM0GoDNfz+6lM0pvyUAM8CqrDaapcm6K2kKtEX3TU=,tag:bc59T0LvR+cxoX/ayDsF1A==,type:str] -DOI_LOGIN_ID=ENC[AES256_GCM,data:/N76Zjh9,iv:wSJ/F6ycYlxoQ0dDJK4zEECrUw7F1uRzQIpaut7XLy0=,tag:O/CHgRWTPWs/MEZf1ADiNg==,type:str] -DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:KPJsTBKbjm7Lh65xeQN4MeynW2k=,iv:2aHPo98X4HSuFWvcbqwFxFLHRekvsxxLCQ081ZNtvg0=,tag:W0f5abzYvJkya/GG96zT4A==,type:str] -DOI_SUBMISSION_URL=ENC[AES256_GCM,data:c/BcNuFqQPbYVi++NUbkbdNV2JYLaFkcmBtzn8EVN+A/HWjc8BlaOOs=,iv:phyuU35fFCr1VLNgKS+0NxoTg7BQoYhOo2MEI9rb5T4=,tag:uEe04YOPrHWTcz6jniLztA==,type:str] -FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:ttl2E8BgOJ9ij+/p2b3XjmHzDGtuSyo18R9QxfQtCug=,iv:baGXWnNtKe5YuKwTYvEx4+tJFAoyohpcEjr+yN714g0=,tag:ZHJj9haQ/gU6lw+GK31tWQ==,type:str] -FASTLY_SERVICE_ID=ENC[AES256_GCM,data:1cqxB+w/8LxrCzkvUVJydCNVv4xSSA==,iv:4fjl+V4U5fAT1h+90lOx3hw1NIuZyO7m/i+2wbZCu+U=,tag:Jkfs38M72erSxnA31qPEDw==,type:str] -FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:308cafiJL9MAG0WrbbF4pWkw/8Pj/1FBMIRvCoFFjDY=,tag:OqsPaBf57YWIxpWH9/lraA==,type:str] -IS_DUQDUQ=ENC[AES256_GCM,data:Ik/xiQ==,iv:lXXGaZyiKVQuiL2NcPDzr/necCT3JAAtVNANJ1Yhpi4=,tag:akOgfXrGkwfp/8BVaCZ53g==,type:str] -JWT_SIGNING_SECRET=ENC[AES256_GCM,data:mzWTmqVKGdMCnV6AMXaTXZOU9ZySaziFpLtRhcwgKGmmIUjb9r9uL+VaptpkMj7F53O61aSiJahGAQ4fmVstzVPr8LV8dm9d1rzkBBtKOy2AV3uNtB85yzuoAwgs5cFzdTIAqrUlYq7kEFiqdZA9Q3OKwdlhihLyTVy/Jpp6nDyxfQzhBGtV0babZz28b/pX0uwWMTkxentuRWPXaogw/fvv+T+U74TrxliB1yNSZgH4AUDs1yrt2St8Kr3N/hobnwl87a6YpsH5QlFZxjw6L8aWuUXXHX2YhGVuuyyrokBaHb/yUs6bnzDsu43LBit/J9QTl1NwhgTK7+jO5azaKZBVDYQmi7OI7BiGdtFSlAr/mLrz+86nVa3mRQJxdTdoEwS7U/noKkkbXYvSKn3jXol6ZauivSYDC5/FVVcluikaHPYhZzD37gSu1TmDEyaTjKDrxKhf6wMUo41Q7BBWjYUF4dIYZkMp16Yoj8vbAkvKDmyfel7qh95ZNaztsQ+isgeduygnWl77cjQQXfQXjNG3JZD+pXQtBgRDaZRgQy2sXshx624ZLxrCxc93SA+ysEgdDxabT8WcStPC1Mf0NyOGckpJ5WshNbv9oLK+tAZYArzhIQHlXPsPEdIXSjEsuCLnRgQXCmRnIwH4dQg/+OycGdCutNBoE2xzCzLwFtpmQCfC6svIVkaFhDxBIQrYvU9JB8xOPKwGt+VmsVSPC5BAXXspk9YgAd1OBK2jwK5rC4WZyN42OG6yecVflJrXBw4vUN/qHtmVGsb6cQ1kqqrwJMPVK7XLrheFqxWbh5Qo5Sd0iiDfeU0QMLbqjZ7PM6QBctZYO7FxOoO3oKakYLJHsuIsydVjmuUAQU2XTkWDA3goD7e4IQcTk/dt96pchQ89slvr7X6GtKZ8xXv/W8fVS556sWgtPGj3CsBgnbKHK54eM562VjX7momuTeyyPvMqnh/2cDH35D2sqmlSAVsF5ZMaqCCvSk3sxgtG47Nrgwr0JfjMKj31qP/tlKrRiOPRCyU4vQj+PNpL+YN6aN7IaTJSJEuZHzMxU3oYFZJi0oaIRKZWGz7mz0AVvjBNBsCWUWr8lNeWBiapqg3dfeyEhr7WC7A1wJM2+uxtTWJtn1QouOh8QwfhoheJ5hFdJJsM3fW/i6hzm42FPeB7YqkuxjSx/kIFzlfg0P/V6Wrq0A/OO4oreXbcXAHes8U9RaZhal/0OBXDvlF2zpnvnAX8LaDU59vPtJ4pD+F9b6g9fwdnv47/FeBJEnyguSYPSKLyB7XbmICPn6r6ACdstj6QxWc6TdTQfxNlMHy/hZ5lUrsYflt7cm4E6FESD0VHHAGzl1O6spg7cYFnEiN/aA==,iv:CgCL+WKalNud/pqASKfp7nmx+0TkUi68OAHtILql8mE=,tag:1pS2XW4Lp7KW7uzgQPh3+w==,type:str] -MAILCHIMP_API_KEY=ENC[AES256_GCM,data:xGUlZ+DQO5MYWoNzIKzU+60AOfikW1xZ0yPYNRP/jxElzxWf,iv:QcGbONp8pHdWTwCZDcA2K/Kcv1d8q8JdxoEB9oGWY50=,tag:BEMdL4hqukE4FEH8fU4VkA==,type:str] -MAILGUN_API_KEY=ENC[AES256_GCM,data:HWakd4g1OjIDcxbRd0KqAD6eBXPIWK8/T51m2ldipfpprNjo,iv:TTGZYCS/l9xs1n4+MED7AGc9w0oiXdn9sq9Jn8j2usY=,tag:+hbpxadiNfoqjR7MXyn+6A==,type:str] -NODE_ENV=ENC[AES256_GCM,data:doZX128C2L0Okw==,iv:xWVM/BmZjaYZAW8p9ZnFBn+Zu8vkqZgY7Dowz4FOVY8=,tag:VtA8xKVXjV/ub/9A2uP4AQ==,type:str] -S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:TTi/idrf7erJ2MP0Ly5WKiM5QI8=,iv:TZartUZmgMyiiq3pgsdzjAlLawNAUlJmdYbrDGCLGOI=,tag:7KfnXeYBbQOsWonYH2633Q==,type:str] -S3_BACKUP_BUCKET=ENC[AES256_GCM,data:BGLP2jKBGO+oYas=,iv:2bQ0SVZbBTQfGMF4Bp5LEcOCtB+c9Ea7pX/RbhcZeq8=,tag:giBYs8Ou2iYKciiv6HQU4w==,type:str] -S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:iRWyMNJIXCLnNF+PkbN08jGaUrcgvdkF01pwiaQP8cf41J8=,iv:9wSXMPJWDfhB1t+7yKYmC9qS7RZ/BnPvYqoA5sRl0nw=,tag:SYXmtg2zwVzd1n0FP1Hw6g==,type:str] -S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:evC1NR7vP8pplfT6aoTrhm+z6NzIW6gJFk+3MXTpvvaBIeV2ZRSh0A==,iv:V0TMCtGnkYLr5k19a3QPyUOAyAvwbe1Wunful0RMd80=,tag:S7pMGtvS6x4dyRMF3spKgQ==,type:str] -SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:sh7B89Ys9jaTqSERyLWvgdutKyFQiXGCmC++cCbPonBTF1Zel4vx/7D/3yyVgB1fD9hnHE+f+vaR9PucgkBaRlaV6h+L55X6jGsy5LKBGKZ1F2SenuuYNy3apZzRjTeWvqamM2IyxscD7bYZE49WeFjDdI/TIadh2h8d0Wj0ioO85nTKaDu/59rBqCcHcxwERwZqdjwLgtEGQhNap4w7n2N4lSKj3riYQzODaMQumFZDuRn7fuNiGBGGhQ==,iv:IHUsQRxm8+dF6p30VUsBK79DrPM3ROCdHkNK14Jb4Us=,tag:lSvhTbS2qiKRB40sWabvnQ==,type:str] -SENTRY_ORG=ENC[AES256_GCM,data:NLcL,iv:T9ACrxbQ+t1F38Iinem4fP7x52Whb1DK3UBGomMvc5Q=,tag:ysro3OF2u/biNgQkJ88RwA==,type:str] -SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:6Nw=,iv:AW/nNSkLutTFL1LCvLpHg/B8at1QG9uT7Vgc2h6PnL0=,tag:Falok1cxL1rtzfdQM7+RCA==,type:str] -SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:ARPx73gaS6aPcu54iBL9iLoqD56bmZHxJd21T8jq7Q86kt0cDQzmXCooOfnySMAZyGu6Dr/3rQAPDos5err2C4e/Vo5Cq9edEHZg7ZlX1A==,iv:LaIjGlLb3du7SIiEaOVs9QH8EmHV0khZD9dirOk1qJo=,tag:aempM2mdF/dIzGOT3ZiStg==,type:str] -SMTP_HOST=ENC[AES256_GCM,data:GXxeWN1vPBoJ3rBcZhLcOBpjrXm6TC0Pw6wqOBTkxmId7g==,iv:9mI4C0+LF29B3LnkcT57N2QZ6nefJdFb/FJjm3v7BNs=,tag:Vjg8oswBxEERkuLgIHOrJA==,type:str] -SMTP_PASS=ENC[AES256_GCM,data:MGIc/933R7Snd6zRCqVpRXGFUpURbHRB85NDM10Ogrzu3BLP2myEz+jCy6c=,iv:DfEFgvs/A+GZhgHtos6Uh2IJ0bco0gXhZFxf1XrjoiM=,tag:zcgatj2yVIxBsilAWkLo+Q==,type:str] -SMTP_USER=ENC[AES256_GCM,data:pDD/xGWqvm01AyFJCKWlJJ6Y+cU=,iv:zPV32VpVrwofsuz8jd7qC/W89v4lhxDp4xaaddHRq4g=,tag:HxyXmJP9ujO57ZBf244lfQ==,type:str] -ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:8M7oq5CzSxu6aB6K7fC8CWEfdNY=,iv:W2yDnL7dA74ALw2ORhxar1mZaTE/Ds34ni/B8GpIJYA=,tag:7in8tKyOxLb/tYWT8Od0WQ==,type:str] -ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:mnLljrjAzBnNKaFImzs000XvJe8=,iv:85pHkDq/R6gSajKQJ0A6x71e/WUySvSjLpWnxp8oNig=,tag:qIP1AQQqUgTVlTT3DQzt2w==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxM1RhaGJ2M0lVZVhIWDdG\ncjVCMEYwTWYvdjMzM0RqSVJTM2daKytuUVFZCmhIcEVSSFg4dHNRYkdXbG5Wenk0\nTVdscmFXZDdSNnd2SG1wdjVTS0dkREkKLS0tIEtHV0JOSHE3eHZXWmlMQTVYdU1K\nNE5nUjJaYk5KRTBRZXUyU2NhZmFOUDQKucEo4hRAKMOUTY1g4M8HjvThhqdWrTHU\nFrMJ2q25jtSNHtB0uNq1fMmV9V5UqbEABWRYKBbgDBucxMp9F6+gqw==\n-----END AGE ENCRYPTED FILE-----\n +AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:Xja6pWJuVFV+Y/aYvSB6pVk5sRgYhGtz38T4XlESV2U3BicdxOzJiblI64hp9ptivW434emiC2CRBQEoY2iGIg==,iv:BkfH04v7u+nQJ5C6oSsbvG7qfclnq4WUvNZJ5LvbcYg=,tag:y23Bn78rh6p0BXzvDrGvpA==,type:str] +ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:gM4u13AU5rGlfvuK1TygB2EHhXgNuM/ymNkYHnUH7F6ibMwIMANf3DPCxKRfAD+Hg6bwFoziroxz93bw7CeSdQ==,iv:BV0GSx28moDNPmGd3Nr0+tl2Kap4Fcn/5P+sNZmmTPY=,tag:Kug/H8MvvINSBESdeVLJmA==,type:str] +AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:vF++ggIsvfjqhnVOXWFAVunjsVY=,iv:LjsUFh31LTLn8LLjVLFPBiqFnbEZutKQZl59yInZXwg=,tag:bM53fg0cur5DoT0OrQGMlA==,type:str] +AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:3XOOm6S08GV3Srzpq4YUarG9wRA=,iv:peWv8G0v/2oc5pBczqP6gMFDM5xAiqQcHTQdUOrtzhg=,tag:St5ojbZCyyluwEGTOt3xxg==,type:str] +AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:1RGXw9lVjLekUyIheth5Hp7ieyOP2J1tgUL1gr9xKP7i9kRS2wCrjw==,iv:kaPbn3r8bCb3Kx4rG512hi5SkopmGI0avvKLbMtfXiE=,tag:vLmLBh3eLyQeHQRoiJcaow==,type:str] +AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:Pdrg+Cny7TbtBD1h3b++V2vqL7jQCDWL6KqMNMHn3ol/0iFyy1gTUg==,iv:b/rit8FMdaV3zByD22TdcnrZ6qHSVjzDYOGvERO8SiQ=,tag:eSqtBcFUuhCcw/6PaLJf0A==,type:str] +BACKUPS_SECRET=ENC[AES256_GCM,data:jhMNkb/JM5C6c9OC+ML0a1vFd1y0ngo9HkSeCmYEKLR8RHal1Xtra49vDHU=,iv:FoJbGu/srmg8hK2evv6QZvBNih2dvG72PIxSZi5ieUA=,tag:T/CEkRDshX53EdWLZhHe1g==,type:str] +CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:YG3oxF79I2Op7m74ioI6fU7qndkls3XZa409iyPe3TgsAb8oNk0sb0UM2pPWuvQd0/lXxiY=,iv:QHQlzlUahFh/BwCP/Qt86fEP6PTZlxCyMNsa1kBajcI=,tag:3YcBpFEVj/rXLjjEh+kkkQ==,type:str] +CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:xhEGXVdEz6AseRerwAXQSZp7G/USThj89U9OMHlNq2pfG8nekHDgoyDQ0cS5kkin6JSKpP0=,iv:YMXXosY3TOjIf7QKo6bd7Soqa4S0ojliNpwn9gVdTFc=,tag:DAn2bpuZg4rnkiAikBa5Rw==,type:str] +CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:7W8jZBTLIMYB6m593J5q7Xs+Zd9fvnn/loX3thddkcU=,iv:lUJwLq4D/eus8rGxC26cEaioVCNOu7RPLQFizR77Qtw=,tag:MtTY3QbPePlPa262w7arpw==,type:str] +CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:bppDCBSkEcnBiCMbq4vRCIHcllN92JPPWw/lVg9pk0w=,tag:0LG4zm0f2qYRkmpwjEY5gA==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:AzarkHcGAEqaIy5jF6fuFGBBUvOqtJwsURw5eQjB73jjsu9edl9kJuJniYs=,iv:nMVI5p/YvwG2ui9Io/0SmkiAUQVYsafgejmKP54eg1k=,tag:Ckzfcv5Bqb9nWSsO/4Zl/g==,type:str] +DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:RtWHvl9rL7czXLByOpD6TMrWV9hWcfl07F9k9S3frHTdcg==,iv:4FgMHgbL7meB7xsoP0ZgDqHzmAYdotGMwB5rjQz0+4w=,tag:v3qVTk944kaCFrSElDLl9g==,type:str] +DOI_LOGIN_ID=ENC[AES256_GCM,data:eJXP310Y,iv:hHm0RYxErafZsNWIp4c3GMkGlwD3WiHIYi1dqDf4MY8=,tag:roFFgJRFOSCp7GLTAqW9Nw==,type:str] +DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:H3gIlkMt/TFB68Qnahf027i3CPM=,iv:zF5rBVnU7mooeiMvxfoCVv9VyNaroy0WvGbukhO53vM=,tag:NL+GYVrWtF7m0/q7kMnduw==,type:str] +DOI_SUBMISSION_URL=ENC[AES256_GCM,data:e10KvblYLyoovn0wTiD7n1COW3codFR+PRD9VcW3VREgkM63SVQM2zE=,iv:L5MWiPcqkIYq3J7rodg3Z0dMmrOZazcKgJwACjl2MgE=,tag:2/5NDyx2uqypSPd+tE0wqQ==,type:str] +FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:F2/m+nPABzbg1IIOu9q29+MsQxIlUHAReMmrIp2u8oE=,iv:T1mSVzxGt3yrVifSsP30KKyufXAQxavCeAl7u1g4tsg=,tag:f9aRwYgw67Ti2UuGBWftCA==,type:str] +FASTLY_SERVICE_ID=ENC[AES256_GCM,data:aSOOgoDfcVnm08avr+zcCLkiO2KP5g==,iv:TExbbOCbHwy1zARIMQA8zIkrMuqrvWMDdq0lob1E2ZE=,tag:aYa5JaThXA1AJdGyiOj6NA==,type:str] +FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:/jadVet/Hz82+n/ApBYIa7KUGQ+IwEWf6Y7+m9h3mnI=,tag:5I7IMYbVqBRdTHkbXefM+g==,type:str] +IS_DUQDUQ=ENC[AES256_GCM,data:/Nx7Hw==,iv:uDInaGYuPtRuTFP4sb6NBPVEwoYKV4/Cqy4Vi7ePuHk=,tag:e3+FWrP0odei40mpHRQRSQ==,type:str] +JWT_SIGNING_SECRET=ENC[AES256_GCM,data:vAGHcGOpR52+MOVWM2e0g5+FVYpeNIq6dnyEwEAn8EDP+iCi+nKI7vGNWL3MYoD9RRFCpDiOQiPPc8NVF//Wzxg/jrCvEhpM1cXYJvdSlrR1wsxAFpEzBUemwbQhOMlcml5wAxQfajxGdsK284QabHc+DLz5eO7KY/549L6vg5xCu7JdMPhY7pjEyZuK8/n8vRGeZ41zipCuztszn0USF0yLbQVr1VpujszMRnKnKjKhL6QVJLC9zSC0jiiKM+/NwrviINRWDRkSPPWENTwx6d/P7zsQ8h3zY5JyCEZ+8O/YHG54KuFAS8JK3ZfM6ttyP6Mv/fRbeDeeK8S7Etdtu89R/n6m+itdd3wz7LDjgn3WsTLhoMrsc2i5vpld6NwhIVoQ5N7Lc2qjhkTUZdSF2WUTCKjtQb2TV2IlnQUGcwIC8SJUPeJY3mVlALnX9gOY1IIeIzWUUs0CO8cFl6lsWnTTU/EtiF4g40G9m0w6B07CAnvClpH2gHNl/+MHMR/Gu4VS7cb7NajToamNeg/s7POTGcl4RYxWpJUXMSWoDP0DPHzY46WqmtqPRfWIvrl8fYUneUfqDpTqGtp2NBzauvbqKxRqQzERE3W5PK65hDzY508Rgf1ybQVnH7/BDiS49UJpzoZ08d8/JR94Q3L9UiBsCweiUdgqWSamvQI8uAy945K59lPKsFT2MNo9nMXnTaAuym0T14kk+eMrNZnE4Zi7vW1k1RxyBobVIBRAICx+ZgcPWesimbu7xeG5QyX0VvWwpMdgF93dULDwGTNtPyzx/wFXgv7YVzTi1ssNMd1hjIkp15lxIq62poNpaO7N2ylcHlrbXMlKwT+bB//WxJg6MBtlHmuC5yH6MzKu2Ffr7BnEcwevH+mmssbbGxkXasAZJpFtJ+BbO6aQMHHz7aN4TsxG/wHD2CLSsMVFNHRo55g1ZT6NNok/uGQLocexlu046Abjd6LArf9t+1U56Td8fguLYZ5t5JnjrpePsRzJzX7h5fRUH/5V/kQOJAiqZ6iB5MomV6tVN+uGUDrOgqfWsG/LH4RkBMtyT3mNEKtJfWLKvA1lO8S9YBB94YJ/FSFMyAeriDg5n8XrlE0z+I1JN3exGRB0+c+Oq0MlWfoUb05v3bpMUWqKmgP1Ata/DCyOyKrZEXhwFY+no3i6bf5j57gmBxxECjv0fyqvJM3AViVKQ9OvyhQoyCYEOVPNfc5ZBogc38pItrzL5a6hB5W4Z7MBeyxhvOe2vaoCPsR4qP1It5K2+3bmtEX4KXTy7tXtkQ4xCg/rvpOeN7ZjnEt3r3MaZEvKgpCWbQAF3JRzFKVb2mtVC5z/lqeffCvNhJN7XP/ztdvrFQNfDEIzjA==,iv:BnISOMJ3PNy/6165fq9oeFykKx666yHzuq0UQkaiZlI=,tag:zkhJAgY0EvR+PmOZLNwOEg==,type:str] +MAILCHIMP_API_KEY=ENC[AES256_GCM,data:NtsS50tkEddOu5T9H2spXFd749cAEsnQZgEh3tgBjRWK+CiU,iv:7MpAPJzuVnaIdHYeHmEN1l6xGgw3+fvUvRKJEX6Kid4=,tag:tW1bBQM0GF6BqbkaY7USsQ==,type:str] +MAILGUN_API_KEY=ENC[AES256_GCM,data:MH6seKkqHR6Wj9JZH4mVmWHF4R3FrKsTat2W+r9tQPVByFIZ,iv:YjlSJ/VbJ09RPs3txF7gs5PEut19/QTn/c2zRZUUxFg=,tag:HikxvDvrLHUuvfPIFJApiw==,type:str] +NODE_ENV=ENC[AES256_GCM,data:eXjT6u9VZktBhw==,iv:y7svJfKogEh+/dxAxH+HLd6mAQyb+eGzjgbuxOkT9fw=,tag:yYUGMmBStX9EnWKgKVgk6w==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:eYFRgCAIWsY8Shhr2HZHCR72r7c=,iv:5M5nftqJi1le1sDZz8YZa32QwqrCxOu5ClZ7knWvQQg=,tag:hE65DX9ap9wCXeisDc54HQ==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:x1ZgjjdMObQ9WFg=,iv:VqTy+9ZCg3jzgU9CtYxGVgLLzGfMnmhqmCHB8dGIo/Y=,tag:kSWhM9gxKQK1jf/XX3mT8g==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:UVnBLUILntsJ+ledQ6auXqP53cAFaDerWgsdGOgOZ84ZyrQ=,iv:iXlsZg56X7yD6Yu9xOvUnSL0kYi3pxyLYTQOhUtv3ow=,tag:QZG374UhNDOw4I4rc4fI1w==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:6c30LpSh1ix7R2GDNCKcenjpEaZ0TuFmzGhiuoOtdYkXYQO7X98jag==,iv:8c9sE/sNIwnaJtdufSOLruvN9tHVUN0B0dVYLvoi3/A=,tag:h2JN5JtLI6B5Jn7/m6QpGg==,type:str] +SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:P8D2K73O9Oc+uPMwIgtB942HOIjvpNSIl/pit7TPpJgM1HW7LMSUf/4BCM0Iu269p6lehrUpf5ZcSIL6yCnotfXRZFY4G0syqXvKGvqiTpklVttg+kjsOFxsHEWBe0vRvnSfyUfqVmrfWAh+83qu6kLrwNlQOrE4FZm7sPijZ6H3AW3KMLqNm0i3zKDgDT1vLEBxc6o9M09MUauHsE5l3qzeQe3kFruxhqk11lOaZIOL+ZjnrDWYbjxNlQ==,iv:XfwOlinRFKp5OKtWeFID/inAWGqMfbReHwLuuaFMH0I=,tag:BPKr/6ghaqka7xYpSn8t5A==,type:str] +SENTRY_ORG=ENC[AES256_GCM,data:adqt,iv:ujBPuKnFCHJYOjTs4gyvdNCSumNo92AzFVAhF6q1cu4=,tag:5iWhjeGmU9KghzzBlYZLbA==,type:str] +SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:q7Y=,iv:S5SKSAzNZJAVbIthcYuqhloyQTvCp6us2gA2qAQKW+M=,tag:mwwPbkoC0pzswsO5iDN6LA==,type:str] +SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:GNmQUBEwYvDbHVMbKs1SKlUa9dLDOr9e1iEdQMR4P0C+pQw2CohRVarg95gL+/AbE2mxRBg4d2v//L0zs4v1SqPT35wOOmNICnzX1py7wQ==,iv:AsMZhFlFr4eD2j2wfrQQD2l/fNxtFxqAtln7Enu/xQ0=,tag:prqSzsxXMjqc4KF3Q3xIAg==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:HSZEWRhNJCx8qsxwo+RKZK6JlbPbjyqOcYhSqiixouIp8A==,iv:u5uSGvmkVfpY8kggb9lhfj02nkGH51KQv9VJCJ2WYMg=,tag:5i2VVrAltQxY2sfH1lAOqQ==,type:str] +SMTP_PASS=ENC[AES256_GCM,data:/LbUwUWza4AygxRJBxV/IytK+v+WCdmIV/XnuhsIBrhI6PtIXwtD1s/7c38=,iv:lI0ZV/pqFg2x01LwtFx01T1Ji3M7eRLPUViSOu9WCSc=,tag:UTZ40VeCJc4QuG20E1WJ/g==,type:str] +SMTP_USER=ENC[AES256_GCM,data:nbkdNZYxZWwVxfliNb2hj3+2mEM=,iv:m8om1mOpz3XdWoFq40JrUJJWXMUWuDZNhLcTjp4zCwY=,tag:jE5smruSBVenqp7QCqBYow==,type:str] +ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:QrW07ag3c1b2MJEkvflkYcZ9Jic=,iv:PTjKpT/HGxgUM0frWG9jLYzyXnyZUx/mwn1a87BNij8=,tag:afxyr2HJADre/rORYA8TOQ==,type:str] +ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:rTqa6mWSJSteDn2HkkpAeKjTRvA=,iv:R9wUAudkXbs9PqO/gXbx8WUihWoV0li/buHhFL0lvQw=,tag:9XDZyDaPzK7SYRO3fp6tww==,type:str] +#ENC[AES256_GCM,data:SjEixq+m46e8vo26CpWPqTcFYSs=,iv:+ksMsnLErAhThWqJa88IP7lowNiDAZG4aUCFMcy+7Pk=,tag:wuFJe+nn7Tsl9VWQtn/evw==,type:comment] +KF_AUTH_URL=ENC[AES256_GCM,data:PQaNOYW1ZSe5hy+2xVnRSe9mPCOe4IN/LuIdhIk/UZSBJ4sd,iv:Pd5ZQGOqEBcqPCi1t1qZQ5qnF8Qcqnteljg6b4tKhwY=,tag:l5OJIQnh8vruOuboIjN5BA==,type:str] +KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:+59Jiev83Fof,iv:hcD8yUMGkiKD22hGFfWvpz1fL6tyAB9yLKVE8N18Xr8=,tag:DQRVc+FYeyXC4dvW81Ml3A==,type:str] +KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:CluRm400lyJf10aNLoJAMWH6xADMBP7DivlinSdqWMeqdU7/ImpDbieNw4adJD9/6pDX9EGRTjEEg88zABiCSQ==,iv:tlwGBDQFPt1Xvl3FHAQb/M60v2/J0QN+QKf71pO31SA=,tag:2nNTBSLXUZ+Bvwd0uVxtBQ==,type:str] +KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:NaWwUjdSLWwZmC0kO3Y7o3TRtwZQm/4Nwiz013um8g9GowbnNTGYwvkOjXQZ5PiMnfn+PaswSDGKR3+W/0OE1Q==,iv:UqeEm1DvkVThTIoY/igLRGngaSAHjwixEIYmdm8fQG0=,tag:QxEbPAQCj2z/tCZ6rgsItg==,type:str] +APP_URL=ENC[AES256_GCM,data:hjPOso+xBu5uNa9Tda+GbDG0qQxB,iv:a+E73avCFL7xmuztLpuFHLah7GQOwtJpO6ljsTW4ong=,tag:2WdXA3bH21ajwzs9ga/Tuw==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBta2RjbVBPN0I5ZUFqSGdU\nTGlSV3NERmg3ZFJPZk5CM3BDRk1TN3d3bUE4Ck1yVTFZTisvcGVYczdrSnhqWm1n\nbU1peWtsSUQ2MmpiK3ZFS2NmUlFNTE0KLS0tIFFtUXlkSTg5VFJFdWtZNDZBVXUy\nNGx3R1BtdWtTcm1wTlkwcFVVcUZMbVkKxYYUSSeUDsK4TePTTQzItWrBJEMoWmOj\nLkITwYDxj2YlLnMC4129N732nNaBFLy2Aruc3XaivO8Fr6eCtvRF7g==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPck9EQUNKMm1nQjhaSFBk\nVElzU3FJRWZXbHl2M2VBSW5ZaWJtcDM1Zkd3CmJQd0Q4TFh4M2NrU1Q3dlBrc1Yw\nQ1dBZXdOYmJtTUpGYk40K3k0ZjFyVUEKLS0tIHFGS1cxTzNza1lHbHZyeW83aEhZ\nZ2JhYzRVNkRQUFpSTDhHOFJBdnhpQUkK4RTqWukZ5TW8vluCWDbfVt7Ft3RctxSs\nzSe6OWr0/9+geSyTS3LDdYnwI881c4utCkLT38iRbDNWrHB+wevhyg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOTko5Y0N1SnBTd2ZmOHZj\nSFJxbzFRR2NLWFRYV3dqbVpQNkMzMnA2aVNjCmJhdnNXWk1TTCs2NkFqeWhScVJz\nNFJEUTFROTQvN3Azakx6Y1pXWEFPbXcKLS0tIElKMjNHN2tEalZESkJQK3FHMzFH\nT2VONFJVZkFiaTliNHcyOGwvLzY2UlEKzWX6cJJxrv+7v8iacGd57UC69iNPbSek\ns/E9E285dy5wF5gVAsA2wJOoEgENdwRa7EAPcBsMI7VOl0bILGWiQQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtbXpUUXJUL2tyV0lzeWlv\ndDd4VW5SVUM2alpxSC9ySDZmTitJVEhlWUI0CnYxanpwaHpGc3RJaU5yaHdlZGxI\nL1d6S3FzR3h3UnFvRVUxZCtrTk45ZmcKLS0tIFZwTDZtNi9qMjRqTWxQYjQ1TzAv\nS1dWeGZHQ1pWcnFTZW9UWHJ3YXBSazgKtiUFAtwXghA0PDm45wWKNY+ZyaDPfC+5\nWFUsd7CSnTnKrFvnIJpJKvxoP2a40dDv6D1/FgIr/2FgOuTP7N92NA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2NGlMTXJ1RExKOHVGUVVD\nQURUUm5samc1cnZSR3JjVE41RmdGeDZhV2pjCmtUMHg5bHE5eGxHTytla21IVk9X\nL01HN01vcXl5dkUrM2l4NWpaUUx1NnMKLS0tIDZIQVp4V25DMEVoYzl1NXNXNW1w\nbnhlcTNDVnBkRkF6eUxFcVVLV0JmK2sKyKBrZGPL5Rx/Q+rpzglShyJcjbDiWJEf\nYOnY5JDDB7P08hRW9nB2zcppXAHVfCeUPAWxsN+4BfAQb8FdO8bqwA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4NWRLUkN6dGk2UFhmMU9h\nTXJuZ3M5U2luV1lJcy9mWktaZGVlMkRMVERzCkRaeG9HbXpPOGYvWUhJTDl2Smc2\nWW51WmV6bFFiNVF3WTBUTVZ1azQ3ZFEKLS0tIFQvaDd0RFZOV3FJVi95MDYrMzBV\nNUk0aWxBRkNDVkduYVhXTGFabWVEdmcKGTZOOe4yUfGjZS3uIe6NZSEjj/u2bpkb\nd4Bd259G2uVrG5RkK7AptKM1QHVsolAwF+MsdlopAux1sQJe75ZpKA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5YjJGMVk1M3ZuYisvT0hR\ndWV0NVl1MGVvQkR3VzV6VVNLVi85aDltT2tVCnI2ZENiWXlDd0xwK1hPR21TL1dr\nWXdoVndlL3liR1F2aG0rNXZPek9qdEUKLS0tIHJMR05IbjlMa2N1RTVkYmhScjhD\nMW5Tbitlc08rNXdWSUZnSWhMQWYzelkK3bPNfxxb7E44O11ehhkIUxqhSEgUoT7o\nrsQSxnZFPuJeXEon1VO8c92SeD+JZy6BzXzYo03gHLRxcVBGput2jQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsb1FEdjNNQmk0S24rRzFn\nR1VjNXk4QzJ3K3lBY3dMNUQwU3ozUzk2aEZvCm56cmdjVEw0V2N0ejFLdk1sWHZz\nZlNUcEcxbGIzUDd6azhTaXcwYzRBUm8KLS0tIHBCWTgwUVRKZUFrWUpIdGh0NWV4\nditXdkFzQmUzRnJOMjBqNW1oMlhBekkKMmDJYcIz3iU6q34K9Ni/NGsyr6piekCJ\nIElWygu7hkVO3lzzZFyMCAZLpG/Y0I9xqcA7ovbkxOtabhoHNU/5qg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGOG1ueUpIektJazFCT0pj\nVzMvbExOQy9jK1psa25Rd1FjTUZXUmw3YkdjClljdmVHdVNWWHZ4MEpzditOaTNs\nRSt3NFVLOFdEQlhlakduTFpDTjF3aVEKLS0tIFh2bFZKUktqcmx1S0hEK1JvYVRi\nUVJTYkpJUitzT1NOaDhjcFJ2NEl6TzQKLtlw9zz/nSfvxHkNTKWb7ZITpKaNP3L4\nGH+FUZ4cQCJgUoNxtrGFeHBpgz5RCfhkCnVg03B7oZwZO4NZk+YQhg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwcmxSVVRqamVmdW9vckxx\nZ1hpWmJyeHNWK3hzTnpkNXN1N1VHakV6TWhFCmlabXdXUkZzeWRpL3N3TjdBZEdw\nZHJVNm9CMDg3emdRQmZSSUYwbEdScjgKLS0tIEFmblNNelQ1Q0d2dmxOdGNjTmxr\nSnAyUGo3Ui9oTUxac0krZmFSdndDMlEKpd4ME/6Lt0muiNUsq1AY+7NFbvUd5Rez\n7XAP+16L/nQsqCX2CE3w+m+ezlL3Xqd10RbTgxhG+wQWtON6NoRbRw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUbHE4Q0JVdHJIMnNzdFJa\nb1lweEROcyt5UlFzNHEwNndpdkVVdDVjdDBrClFNTVd6WkJCbkpUSFF4ZWVaRENS\nYWhsTURhaXdSMnNkU2REZDBONUZDejgKLS0tIHFRN1dXQzlYTzd3TTFtVTJGcDd1\nU1hqUkN2dFFSNVl0d1F3NDNtS3B3VlkKpN8Gn9oTEjCozAiYXT0ZUdXAc2wAKaED\n8oJ5r7aRuEsjidoBVNK6fgb1Jgk8qpa0g1/A0SSQRLXHspDrEz2HYg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_lastmodified=2026-04-23T17:35:43Z -sops_mac=ENC[AES256_GCM,data:bLZQR9QReCmUAYgBkxVBdZmw+3im+oo5G+lxghn68KX5vRczkf2tS2cyFOhirXD6PvvHKbsQpZlB8bHoF3uD2bsuEzdPpbtawYlUJIrh46uK9b5MbzfzqNUmKV7i7m4AHaOYxyly3PebRvUzbgoX/A/R8fYvCNz/vemaBWZzc/A=,iv:MGRNy+Q7PZcs9NpWS1XeBdOOBvUbErkEeJWCesGa904=,tag:wQhGaa70yAn4eV8Z9BgbJg==,type:str] +sops_lastmodified=2026-05-17T02:33:30Z +sops_mac=ENC[AES256_GCM,data:mz8tWY+WNLhcwTxQo+NvxyDLKHLVW8T0SrGrxRUnqzVuF/RtmEF/cXt/tNuB91tt8nCXNRHxGmvzNglUOytCVGZwZ9pB7rMMY38EcEYGn2weQS6zhOXFg+dX31HsgYa5/uMqPrOlmGEb1IHSj0lRtRMZ6HNakEaQGd5rGzQdzGQ=,iv:cn0cIvKtW16gKIy8p72soVuKJdsjZvSEnE0yR0umxNM=,tag:UdbXCfyxx4p7CmMkxNLgSQ==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/infra/.env.local.enc b/infra/.env.local.enc new file mode 100644 index 000000000..3b8465871 --- /dev/null +++ b/infra/.env.local.enc @@ -0,0 +1,59 @@ +AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:XRrbCOuZdK8OysMC8g+jAaifrbpYSxVywFEbb3HjnUfoHizjXn+NlWOUAVmpqN5BGo8diM34p2EVduU1XZyzSQ==,iv:jqCPzyTAdqSGOiCFMT88AkvZFYVeEfcwRfu7IwpBlsA=,tag:Dm7+P6d5FXCH0iC016L0Tg==,type:str] +ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:XuAZOjWIOkJ07lO4XrAvlUvn693sA32GWSF4OgdkG3IJqr8UBEtSnuIcEIbdcF9/ANLBoWFgW3PXrZ70jdGDYw==,iv:3MfHLwJisbdD1FuNCN53y04pGsrZdHwFKvWZ4TiUCXw=,tag:TpZf6DbGqzTKqeaXVmmO5w==,type:str] +AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:n5D8UKoeZwKWuwMXuSlxOm1TRxw=,iv:R4d7oQkeBYixr2QzugIvPvbuy8fViR2FcNiPw7ielhQ=,tag:HwWiw9fDS6o9Jn2nKYs79A==,type:str] +AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:b1YKnZq9gAs6unTXjtfsOYOq3Rc=,iv:OFm9qoskNddUO+Yh5V+tY6lDbhvF74OvRFRR8gvw2Vs=,tag:F37DWCeP/BbV/mL5Jt8xiQ==,type:str] +AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:Q7HVjPiubI93ZRPzVMmAUfqnB5wQmDlyMiL3ZuswImBmSPigHafEcQ==,iv:VBi7efe5hJ9cqfyS48w/QZNekyOA1Gd/Kb2I7r3MLro=,tag:9LNAKj/DWpK8B31abTjq9w==,type:str] +AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:dDjdPGyr483URjl+YkPmxtYsoGxhkTjFMBvdJShK43WdYgvWaFQNJw==,iv:myfObHUHH0m7nWYzrr8RrphaBffuqOuM2qdTYUASI2E=,tag:+bsEDlphkGMijOdVvMhzjA==,type:str] +BACKUPS_SECRET=ENC[AES256_GCM,data:oO4V9V5YSE1bGYv/Kr7Vxb3Xw/5qR+SveidcUmbdnBzuZhaCtde/MUZbmNM=,iv:bz7Pxf4P0XX7pDFT2axgjiY7ONner71ZWiyqpDzwk5o=,tag:be2p6MhRvvO1rYoPx3Q3fQ==,type:str] +CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:rzOukQvL+WIJ35sOLwlpX+xxUp3fmhVOhPzmQrqgVKI/Awrzp3bFdt0pPTcpnxQ2IirUCvs=,iv:RQF3iClC4OivxbvCP1jVmsduPcB7IRAsQS+VZ537M10=,tag:10AxZDcl8L94X+Ku76CgrQ==,type:str] +CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:iFvN6NpQH/KJ2mjPPcL38kM33Z7/eqbI6+uebsYuLVEs7F3yh/FOuoueb9TaTHn3DHUkzQ8=,iv:BHlee1tudmEAk3gfUkjNFftRBnAyeB8wH/pwcXxgVBA=,tag:H57LDqLFp9RH8G8vQ0NwCw==,type:str] +CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:9NbqU9+RF1BTNYBJKYoiFpckgEmQIj2RXkVNwOxa9x0=,iv:qGRIa+opCaodDmwwj8z3n3ppu8RWbzrnQZ2tG5d5s60=,tag:t1YW0H6vr+1+ObdHJ0hEDA==,type:str] +CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:MZxp1vXHUtQbnSRC/1T7tRAVYTu8I2sIEwzkn/9srDA=,tag:skSW6FWPkMGvJrZDE8434A==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:7G44ivPxFMcsHTnssCzLCJVS9IZt5CnFpxIHIZahMEol2N/As1mN6qeb3ds=,iv:ixUkDZPnSchGhXQN/feorKtgSMdnmpBy9XeatJE3lPE=,tag:LBu5Rw+nMedECjwpP3rqag==,type:str] +DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:S3j/ilWNZs64uWEsV4qEvft07mXoTbs7ktheR20RiZwzcw==,iv:avmtNQWnEr/1svDYhVHxcdUg5IlGlm1znuYF7k4EglM=,tag:QBcmlkfdzHayGMfCS0377w==,type:str] +DOI_LOGIN_ID=ENC[AES256_GCM,data:tj3fwNl0,iv:Ndpq/BV/V1n5sdRdnOIpd5q8KdaGxESLI1gZDAr3huQ=,tag:FT+G0TYVYPdouH8L0jawEg==,type:str] +DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:6oVZvxbpyGL/uTE8Fs5TItys5ng=,iv:OiPNIe2OwBb+hsrihtrzAOZRq1T1q8TnOebgH/JXqsM=,tag:uYsY8P+/RA5hKwoayTLQUg==,type:str] +DOI_SUBMISSION_URL=ENC[AES256_GCM,data:CSrSedSfIY/JjhC7EPGIrDZsjJ+3hnPh+2jZuR3QLYkpIE1Otqu55A4=,iv:SLpluAPerRk1fEypwZMSQqEAiVJtjoiA0esgFQ2+mcs=,tag:HOt7XzTHNe9WGx832UKibQ==,type:str] +FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:q3yhtanJhbVPpkyTbUUT5XV4WJqvCCKbkIILsgecnG4=,iv:vjMlrhllN0CwVepu4nb5nTVNgL8NR6w/wY7Xu0rLB1A=,tag:NofwqwvS8y90R0sq5tiI5Q==,type:str] +FASTLY_SERVICE_ID=ENC[AES256_GCM,data:niSYhU/1i0ydC0hOglDNMx2wuWsCCQ==,iv:tjat4pUfC8/go2YQq+ZMUrbCopnfgEZ6USRY3fotoU4=,tag:Ivfr6TaLGiQ2/OSdUl8hVg==,type:str] +FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:0/5MlEPOUE2BZzhpqUqBXVsBy9jVRtzrAQqQZftSep4=,tag:sZeILTq2Y1DsvI4axQKNtw==,type:str] +IS_DUQDUQ=ENC[AES256_GCM,data:1qc8rQ==,iv:+c7K3tT3+TkXpQgsxwsUdrduoe0vbmkYrhzka94ydgo=,tag:uPUCSDFj5Upz9DncVa4I0A==,type:str] +JWT_SIGNING_SECRET=ENC[AES256_GCM,data:Wk5Mo7pnugtsZTiN1+RrWlVHfYrYuRLhnxLZaKfoN3dXztCuFv0BVX727APcHi8G5/8l2aydXZJRq7czlXzv+t5Ss81PqT3AidHR8/jLRuy4nonAO+mhMapgafO8cev0ezfOIV/ng1PQS13JRBObn6aM0oLeTZKFtGBpKYP8FyRLjAb2HlpIysQel4SAOR/1aUtNx1lPylunNVs8TjmKFFk0b4gMwdI1BBXldKn1bV/y8BkmEWk3ZkKYh9bloE71lXj+VSdwzeFk+cbJYAfPl4yUV+Ss/T+vdjjst5XrLQehZBXHJ9i8rInP5WcBfzMqXjxm61q0kPAyALQ7yLk1+Pi/OL+EpVAQkbyVJraMO+49EQsNGh8DFTKfAdW9Ef1PgbJbv6oyU3b3940/xaJeQgbDxJ+XICJTtJKRaxC+DGVTg2EgnxU+HzmWKKjHnxGLt5EP9KxKhp8NqSh6vBnofbrFtEybG8EsAPiCu0XHHhIWrUhx8ShWAAnoAVwQT6pkp06/n6gssK1Imoqi5YfJcFdoLWCgZA+8Sgu3rdrqqBu6744fgSp2HAxJ6pfT197Hd8mIyRegVtyibPxjigPEIb1kDN2b0cwLknXH9ywErNCqL63RBpxbg7EfuHQhCKTgwcEuze9fOGxWPFNWWX9ilYasj7D96q+NwgX2iuFs3unuGpx0Qekl6fMegkuKw8MKeG29GMvWqbRGsq35VWTPR2cMJZ5VfJINCDu5NbI5FSGS0doSIRzVs/fYfUZ+TKgeZbVM9dY2PETFmVwXRPEgxeIxlP1okjsPP+bZnRmzNED5yOunOo6o9cK7E3G+gzsuVKQW8zLxP80HbNcI4fAflOAyligJpBh15A1hKNdhm63n41h6kiGecX+gorjb+q141oIq+CCsU+AcGs5yGJ1+VoWbA7FFLd8n8RNaa3NXZyiL0qsjGg6+EzfFwdKj23ChLK4r+Tqw6LPLYtsHF/KkVQbLGxwhNpDs0aZT4hJbiCpjV6N9UyuBDl9HEG/loYvHfswBLbnKoagB76qFCWuATUAeJEixFAA7niuRdEaEhiNapFOgKfHFpZ3ZcfGQn2++IsemEeEnECeS/aRTM/3p9SNNOv6c9Z7DUCVLAELrRqi/mwEg7BNh3amyt4cE7NiW6Mh1zzrFW9jl4Ny8G2AZiydjkTlmNcXJMSrU5z/bxWqdWQrKCtBTYkLCxnv/xLRHxF4F+m0ZOcJvESXnyWgzDFyNkrPOKNOQGO8rhDRQpu1ET4+SqbIvyhIiloT9U2ugGhzSJB5YW8wn9/f0rb9cFcx7r4kbhFerno0FPHUoyoQ+8RCoXcvNkdaL/3uiJYE+jiBiXDUvnCEbA5RYYHjafg==,iv:PP4PbASMABdy8mQeNpyWI9FeMjWhRNJ8nycgiaLXBKA=,tag:igUoRywjNWwreBkgdEW36w==,type:str] +MAILCHIMP_API_KEY=ENC[AES256_GCM,data:CIWa32ygnpkK6QPoq0FVw9YWaXZrd2LFbjOYDTrt2wOACfmr,iv:ooTxYS/0p3/6TMde7+/Y7w+h83CEAzcRDTIXjz5lZ/Q=,tag:s9pG1ttTwSiVtLXnatdjmw==,type:str] +MAILGUN_API_KEY=ENC[AES256_GCM,data:nPH6Km5hixRtj+btl7WHAUYGkONxHmcDbf1H/Bpt0TWgC47K,iv:aHpsZo2EAzPzjfg1/mp11ucH3qkQaRQfySM17PlxV6g=,tag:3cuYn5jLt3F13Scq+nmZNA==,type:str] +NODE_ENV=ENC[AES256_GCM,data:1isuh3tOeAJGcw==,iv:AfPbLyMVmBArq26Fs01ZmMc43HMtws2EnQXlOwsmESM=,tag:DVyzGVtVutoSPGL4ZeIBFA==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:IINWnDfgucDNxdFzpZRcOduZaeQ=,iv:aIadDQwcnXocicLhMf2pEeUQJu9iqgprkL6wGYFe+Mk=,tag:6Yyg5aHJcYR4yk7I/0jALQ==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:aq9F254VFxzc0Cw=,iv:0wltZ9vfiP8tEgf06FGxQP4y8KdZ7U3rS8csQiY4y5Y=,tag:aYs7N/azr2+boByKnEJ6iQ==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:DdUQumcLYE7SZegJXxUcsdOKz37KGenLXy2fyzvbDj5DG/E=,iv:JUsYadOwVEqpGsIa6mEzvLQ72ISC1F59HnTxbW5mJIg=,tag:Xj2HVARtYEPRT+BB64F27A==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:4/naQ8RAJSEU2caBe1P4nHTL7uG202WyXg6M15ZfiUXGs0d4dwzWnw==,iv:7N/t00CkNVMdMS6RhYFICxRu/jW0DtCGvvGYNPmBWCQ=,tag:LdomInGvbX54AutRFxJdEQ==,type:str] +SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:u7mXp1lTtqGIx1M6HfJKGh+/S0nRWvrtAVJe4iBUdKvaWFW8/KFbjHOE6/RQSf6LTVVpdsfl63YVAVLlH2ghgydz3sh9hfALJQTiOP+0CjvjMKYhEo440zv6sEp5kjnJm9rac7DG8bYGjmrJCwMmmsS4xgXDboGgzH73gxukHodyUEL80LdmOQe4mbvHSHePcK+xWymhfI3n2XEQpFtlccIYYrqWFTUxGchF71OsplQxZ9nBoE5XG3VHqQ==,iv:GR5L7mr4dH2q2ldnpTjjJaEA/FeaUnZZoMhCaZd1SgU=,tag:ZI6OUhh61uq3CFvU3Wt0Yg==,type:str] +SENTRY_ORG=ENC[AES256_GCM,data:X2C1,iv:8yd1SEWhJaI6cekgE5Ia/J257OXB4qh7YTgRolFDpxQ=,tag:nO2NzUHCDecfpvrKEtE0Vg==,type:str] +SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:8no=,iv:Kl9l9odW748QyrJaryvDadS1Fkd1jEB3tFQmhBRHE6k=,tag:qVJDNkOMtpIzClxnd1wzLg==,type:str] +SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:qkAAX7U3crQ70/0/MaGOpHfPUH/w9w1fidjLelKIoikNzP0L6P309TQSLCbKp7VDptW7/N+Oi7pZTDohDp2ttod6C2jPp5IOVNWE7mRsEQ==,iv:mXHfd0fBQULasBu1rlxxdbqV/j5qrHMHHE60vPuiAhM=,tag:DD5QdKWP3xqZmPq99vA5UQ==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:R4KgQe6/5afRANw/Kn1d4ZzY0s0HeyVWkGdx9lPPnhbIIQ==,iv:3I9jJLlj786XEUN5toTOlC2LmMC4hSK1FBBMouShBh8=,tag:KZEHGBB9wUlOKPjLCG+ZzA==,type:str] +SMTP_PASS=ENC[AES256_GCM,data:fnsP0UPfDLcpOjXl4LGeVKGo7RajA4d7HHNyYSc1c0WgcvX6DlOK7/25qRA=,iv:nsvsFK8QS5h809xUY+1s1SzcA36HQkDPbeHM7ClUqLU=,tag:duccNhq5B5gIJvuHZMakkg==,type:str] +SMTP_USER=ENC[AES256_GCM,data:yQorJa7k/UKc9AoQPcb1ckqdlt8=,iv:e0rsCh/UcNcfeQ8VKJ+uJwoyndieXc7a6+yawXMZrls=,tag:oX3nOk2EpYOV4ulcBALPPQ==,type:str] +ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:fkual9M7pkW4txUAltrX/tD68h8=,iv:1mPi+2+uYPl8H5N0UF534eVCs0dOAamSzm4NPQCPRjs=,tag:xQWloLt+CyDNBXlZVMLfdA==,type:str] +ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:CUjzaTb0VbjLeYVuy7KPHizZO0Q=,iv:7jE6QAtqysUaZReAzHRZ+8xxGJeV91drO5WwsGR4bXs=,tag:hNaHJ3r0GZp3w5u++mnoVg==,type:str] +#ENC[AES256_GCM,data:V7juNWxQv+I0TtmWJLnwUkOjx2k=,iv:cnoWnTmHi6M60F8ow+m9lBOyXFtI3oCN7VotEwVyQoU=,tag:fxVnVAswhFRdRiWcOf+3hA==,type:comment] +KF_AUTH_URL=ENC[AES256_GCM,data:qeMJ5xv54dvFedtgr3lV7K/m/lc4,iv:/4N/v3kQb+wv4PmOm77KbpexrUL0MJwO3817cGUOYw8=,tag:QiLkz5XtE1kQhBMqo+o2Qg==,type:str] +KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:VLNknIRyka/w,iv:/xUAi1wHXKurI0S9ZTqQ+dr5GKHuEhEP1ef6IUXQfKY=,tag:mLTbofx/DA24V/o1vMlgEg==,type:str] +KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:SGTsRe3QYFCcT5KwszFBBsC6VrKeHzTQakFYt2imMz0wuVWRsLjEMgfOKz4ZvLal0nBsxve3+8FyfJUSpVQcgQ==,iv:F6CPMbfUKMn7i3EnKtQ2HOKNiVFyfU+ANQ3EQNG3FN4=,tag:HCgyY7QtHpKjMn8OO2f6Qw==,type:str] +KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:LDgrx6R5bsUTCAGHdVZtwT6wjebGqoJlo7lr/Q/dnFk=,iv:nIh12E7DPPY15Bc4/o63HnbbxFAGptahSlZoilr+zO0=,tag:vaTXyeG5ymBuQIwumIpqyQ==,type:str] +APP_URL=ENC[AES256_GCM,data:2tvfTs4bnVCU6XmIoPGdX8jcZfiW,iv:DImM0gX/v/EZXaH4ymBLh8UvSRWyoBkjzVqE+1dx/ic=,tag:+AOWRnr0NGHOAJfTPpr5UA==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwOUZ3eGZKN3RWNVB3c3FR\nRWxxeTUyVXV0UlZlZlVSWEQvNTFtelZFUGdZCm5Zc2prcGFFWjhieTZrYVAyQUN4\nUTB3dTFyQlVSOERDMWpLcWk4R0R2V0EKLS0tIDFGbTgzQkEzMnJuRXVrdWx5MDdx\nZEE2S1pXRmZzTDRBekw1TVFFb2IvM0UKZ39NqCi5GS7bBAnpTKeRw0LOrTt59xpd\n+LE7iOT/VMI/iw1SUTv95JyPjaNLK+Ve5b+niAyhWcV3rOO/Z5tUdw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNdmMxSWlyYUFLZm0xWjBj\ndjB5MFFROHdad3ovVEFobVFNc3d3L1VCaGlrCmw4U3I5QkFRcjVoRFpCd1F1ZFpl\nYUhTSmZtbDUwK2Ewci90VG44cFlsZ3cKLS0tIEU1NnFLdTJ5Tk1UOUJRS0tLU01O\nZE5oYWROa0pPa1hHY3I1VDJwSk5kZVEKZbKKwgkCtQZ8z/HUFjN9oRZYo21qbi4R\nRMSpWxHizxEEt3VUdJ4lWaBDqeuc+b4YhS3vYW+6Z078Vuhc735qag==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5alcvZVdKMmZWZE51bFlP\nVndoMlp2VURUd00yYVRKQ25Kci9uUkJpZnpBCm83OSt2WGRzaHpRTlhwb2xDSDRG\nWFBnM0luWnNMRTI5d0M2Z21WNUpveFEKLS0tIHNyZFZQdW1WdmVaSkFXV21oRTJE\nVEJXQWZaeHRqeE5ncUZCODZhTU9FY2MKcXs55f9lmq8thCt6XtdR4pPGBIM0nOhs\nyb/hxCd5dSnXxpwh1oV45pyFqy44GiEL0Qzccl2VsQsTbvSOgVgacQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZL2hHdk9PK05iS2c3Ull4\nL0x2SXl5OG91QzlwN25DZ3NnU1JYTmJtUFRJCm9OSVJkNldPRnY2VXRBZE5UNjMz\ncFl6TVpDNFFiR1JPYW12dVc5aFpnb28KLS0tIFNkekhwL3B4RzNOV2hveXI2SHZ5\naE9BM3pLMlEweEZLa2dCV3J1eDh2VnMKp/NkvwDan14XxLtX91U60uQ7r/OuUySC\nAoRmRhOVBOwFhtC/06rdL8wf9AM7TPKV9uwMuIopNW2qFovs8x23Sw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqb0VsSExNRG9ONlZlb0Vy\nam9jdDFqamN2Q0YybTBKK0tqQVM0NU9kUWdjCjJVZk9hb1lwdHNaQ051QjVLU1Qr\nMFR4bXZCaFptMkY3ZzhEdk9hVytXVjgKLS0tIFB5UnAzWnhOcGtEMkVyd2ZxZVBS\nZlJzVDFSTXFxc2RSWk5BWk81N2MxajQKIGxV8cHYxKmZ79Fp7r4RMt8FJ93znZGo\nmBuEvkO/M2EctnjO+SQDQIbhNpx/G3++3LmrdLShImQ+fK/lDdHNfw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnNlpDaUNGMDlycXZRUG0v\naldWSUp6WGwxU2lVUnFwcndCMit6eGJSTkc4Cm5MbE9uejdtWGVLUmgwZ2VwWlcr\nbHQyRWVldm1CVWVjZk5sckhYZ3ZrVTAKLS0tIHRDbXZOa0RXZERNZFMraVptTlpO\ndU5yeEFwelBPeDlReUYrSStWZDlEQWsKQ711aKw9Jbcq88IczNO/1esls+JRaV1S\nPIyPCTL2YTuO3SSpHZIq1OjmHhNmDN9nEQ8sw8Hg2FmHvPEfozrxZw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy +sops_lastmodified=2026-05-17T02:34:04Z +sops_mac=ENC[AES256_GCM,data:h/n+dajxY6dwasJsndS96YBIdlMX07zbF6gC1Ecealo2Zgn7YQDT7W9hYebJiClf338j87x9NSHbDL5m/TUxMfKgANziXViP/7EzBMpoEEMhQMXwXz/Ul6sRYfByMZI0kDyiZy8FiMX+hwc2elKZdI1cKcJST+VCnLBNZbHaBz4=,iv:OqlNfTwi/w976o43EOE/ZLvIYSodumuhfyPpVPrlYY4=,tag:qrPSlr8rB17wV+wn8GYzoQ==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.11.0 diff --git a/infra/.sops.yaml b/infra/.sops.yaml index 86ab8b2af..a03f7aa73 100644 --- a/infra/.sops.yaml +++ b/infra/.sops.yaml @@ -1,5 +1,5 @@ creation_rules: - - path_regex: \.env(\.dev)?(\.enc)?$ + - path_regex: \.env(\.dev|\.local)?(\.enc)?$ age: - age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr - age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj diff --git a/infra/docker-compose.dev.yml b/infra/docker-compose.dev.yml index 6d069a00c..c3511a261 100644 --- a/infra/docker-compose.dev.yml +++ b/infra/docker-compose.dev.yml @@ -7,7 +7,7 @@ services: dockerfile: Dockerfile target: dev env_file: - - .env.dev + - .env.local # healthcheck: # test: # - CMD-SHELL @@ -38,7 +38,7 @@ services: dockerfile: Dockerfile target: dev env_file: - - .env.dev + - .env.local environment: NODE_ENV: development CLOUDAMQP_URL: amqp://appuser:apppassword@rabbitmq:5672/appvhost diff --git a/package.json b/package.json index 508b3afbe..363c55ff0 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,10 @@ "continue": "concurrently --kill-others \"pnpm run api-dev\" \"pnpm run build-dev\"", "secrets:encrypt": "bash scripts/confirm-encrypt.sh && cd infra && sops -e --input-type dotenv --output-type dotenv --output .env.enc .env", "secrets:encrypt:dev": "bash scripts/confirm-encrypt.sh dev && cd infra && sops -e --input-type dotenv --output-type dotenv --output .env.dev.enc .env.dev", + "secrets:encrypt:local": "bash scripts/confirm-encrypt.sh local && cd infra && sops -e --input-type dotenv --output-type dotenv --output .env.local.enc .env.local", "secrets:decrypt": "cd infra && sops -d --input-type dotenv --output-type dotenv --output .env .env.enc", "secrets:decrypt:dev": "cd infra && sops -d --input-type dotenv --output-type dotenv --output .env.dev .env.dev.enc", + "secrets:decrypt:local": "cd infra && sops -d --input-type dotenv --output-type dotenv --output .env.local .env.local.enc", "start": "pnpm run build-dev-once && pnpm run continue", "storybook": "start-storybook -p 9001 -c .storybook -s ./", "test": "pnpm run lint && pnpm run test-no-lint", diff --git a/scripts/confirm-encrypt.sh b/scripts/confirm-encrypt.sh index c347f5813..689669209 100755 --- a/scripts/confirm-encrypt.sh +++ b/scripts/confirm-encrypt.sh @@ -9,6 +9,10 @@ if [ "${1:-}" = "dev" ]; then ENC_FILE="infra/.env.dev.enc" PLAIN_FILE="infra/.env.dev" LABEL="dev" +elif [ "${1:-}" = "local" ]; then + ENC_FILE="infra/.env.local.enc" + PLAIN_FILE="infra/.env.local" + LABEL="local" else ENC_FILE="infra/.env.enc" PLAIN_FILE="infra/.env" diff --git a/server/kf/api.ts b/server/kf/api.ts index 505a8d66d..3a946a252 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -84,7 +84,9 @@ export const router = Router(); router.get('/auth/login', (req: any, res: any) => { const communityHost = getCommunityHost(req); - const returnTo = req.query.return_to || '/'; + const rawReturn = req.query.return_to || '/'; + // Validate return_to is a safe relative path (prevent open redirect) + const returnTo = typeof rawReturn === 'string' && rawReturn.startsWith('/') && !rawReturn.startsWith('//') ? rawReturn : '/'; // Encode the community hostname + return path in state so we can // redirect back after the OIDC callback. @@ -177,7 +179,7 @@ router.get('/auth/callback', async (req: any, res: any) => { domain: '.pubpub.org', }), ...(isDuqDuq() && - req.hostname.indexOf('pubpub.org') > -1 && { + req.hostname.indexOf('duqduq.org') > -1 && { domain: '.duqduq.org', }), maxAge: 30 * 24 * 60 * 60 * 1000, @@ -190,7 +192,9 @@ router.get('/auth/callback', async (req: any, res: any) => { Buffer.from(state, 'base64url').toString(), ); const host = statePayload.host || ''; - const returnTo = statePayload.returnTo || '/'; + const rawReturn = statePayload.returnTo || '/'; + // Validate returnTo is a safe relative path + const returnTo = typeof rawReturn === 'string' && rawReturn.startsWith('/') && !rawReturn.startsWith('//') ? rawReturn : '/'; if (host && host !== req.hostname) { // Redirect back to the community the user came from @@ -460,7 +464,7 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, const analyticsStart = startDate || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); const analyticsEnd = endDate || new Date().toISOString().slice(0, 10); const pubsMonthsBack = startDate - ? Math.max(Math.ceil((Date.now() - new Date(startDate).getTime()) / (30 * 24 * 60 * 60 * 1000)), 3) + ? Math.max(Math.ceil((Date.now() - new Date(startDate).getTime()) / (30 * 24 * 60 * 60 * 1000)), 3) || 24 : 24; const community = await Community.findByPk(communityId, { @@ -539,10 +543,10 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, FROM "Pubs" p INNER JOIN "Releases" r ON r."pubId" = p.id WHERE p."communityId" = :communityId - AND p."createdAt" >= NOW() - INTERVAL '${pubsMonthsBack} months' + AND p."createdAt" >= :pubsCutoff GROUP BY 1 ORDER BY 1`, { - replacements: { communityId }, + replacements: { communityId, pubsCutoff: new Date(Date.now() - pubsMonthsBack * 30 * 24 * 60 * 60 * 1000).toISOString() }, type: 'SELECT' as any, }, ), diff --git a/server/kf/auth.ts b/server/kf/auth.ts index 00d91c57d..8e171f533 100644 --- a/server/kf/auth.ts +++ b/server/kf/auth.ts @@ -1,17 +1,13 @@ /** * Lightweight OIDC client for KF Auth (PubPub edition). * - * Two base URLs: - * KF_AUTH_INTERNAL_URL — server-to-server (e.g. kf-auth:3000 on Hetzner internal network) - * KF_AUTH_URL — browser-facing (e.g. https://auth.knowledgefutures.org) + * KF_AUTH_URL is used for both browser redirects and server-side calls + * (token exchange, userinfo). */ import crypto from 'node:crypto'; -/** Browser-facing URL for auth redirects. */ const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000'; -/** Server-side URL for token exchange / userinfo. Falls back to KF_AUTH_URL. */ -const KF_AUTH_INTERNAL_URL = process.env.KF_AUTH_INTERNAL_URL ?? KF_AUTH_URL; const KF_AUTH_CLIENT_ID = process.env.KF_AUTH_CLIENT_ID ?? 'kf_pubpub'; const KF_AUTH_CLIENT_SECRET = process.env.KF_AUTH_CLIENT_SECRET ?? ''; const APP_URL = process.env.APP_URL ?? 'http://localhost:9876'; @@ -79,7 +75,7 @@ export async function exchangeCode( code_verifier: codeVerifier, }); - const res = await fetch(`${KF_AUTH_INTERNAL_URL}${TOKEN_PATH}`, { + const res = await fetch(`${KF_AUTH_URL}${TOKEN_PATH}`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, @@ -114,7 +110,7 @@ export interface KFUserInfo { } export async function fetchUserInfo(accessToken: string): Promise { - const res = await fetch(`${KF_AUTH_INTERNAL_URL}${USERINFO_PATH}`, { + const res = await fetch(`${KF_AUTH_URL}${USERINFO_PATH}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); @@ -136,7 +132,7 @@ export async function fetchUserOrgs( if (!key) return []; const res = await fetch( - `${KF_AUTH_INTERNAL_URL}/api/internal/users/${userId}/orgs`, + `${KF_AUTH_URL}/api/internal/users/${userId}/orgs`, { headers: { Authorization: `Bearer ${key}` }, }, From 64e1506266879112bf0bda6c947a2cf9a4ef7f3b Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sat, 16 May 2026 22:56:48 -0400 Subject: [PATCH 04/16] lint --- server/kf/api.ts | 224 +++++++++++++++++++++++------------- server/kf/auth.ts | 18 +-- server/routes/index.ts | 5 +- server/routes/signup.kf.tsx | 2 +- 4 files changed, 152 insertions(+), 97 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index 3a946a252..4972b4e11 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -17,34 +17,23 @@ * POST /api/kf/transfer-community — transfer community ownership to a different KF Account */ -import { promisify } from 'util'; import { timingSafeEqual } from 'crypto'; - import { Router } from 'express'; +import { promisify } from 'util'; -import { Community, Collection, Member, Pub, PubAttribution, Release, User } from 'server/models'; +import { Collection, Community, Member, Pub, PubAttribution, Release, User } from 'server/models'; import { sequelize } from 'server/sequelize'; -import { ensureUserIsCommunityAdmin } from 'utils/ensureUserIsCommunityAdmin'; import { getHashedUserId } from 'utils/caching/getHashedUserId'; -import { isProd, isDuqDuq } from 'utils/environment'; +import { ensureUserIsCommunityAdmin } from 'utils/ensureUserIsCommunityAdmin'; +import { isDuqDuq, isProd } from 'utils/environment'; -import { - buildAuthorizeUrl, - exchangeCode, - fetchUserInfo, - fetchUserOrgs, - KF_AUTH_URL, -} from './auth'; +import { buildAuthorizeUrl, exchangeCode, fetchUserInfo, fetchUserOrgs, KF_AUTH_URL } from './auth'; // ── Helpers ────────────────────────────────────────────────────────── const KF_INTERNAL_API_KEY = process.env.KF_INTERNAL_API_KEY; -function requireInternalKey( - req: any, - res: any, - next: () => void, -): void { +function requireInternalKey(req: any, res: any, next: () => void): void { if (!KF_INTERNAL_API_KEY) { res.status(500).json({ error: 'KF_INTERNAL_API_KEY not configured' }); return; @@ -86,7 +75,10 @@ router.get('/auth/login', (req: any, res: any) => { const communityHost = getCommunityHost(req); const rawReturn = req.query.return_to || '/'; // Validate return_to is a safe relative path (prevent open redirect) - const returnTo = typeof rawReturn === 'string' && rawReturn.startsWith('/') && !rawReturn.startsWith('//') ? rawReturn : '/'; + const returnTo = + typeof rawReturn === 'string' && rawReturn.startsWith('/') && !rawReturn.startsWith('//') + ? rawReturn + : '/'; // Encode the community hostname + return path in state so we can // redirect back after the OIDC callback. @@ -188,13 +180,16 @@ router.get('/auth/callback', async (req: any, res: any) => { // Parse state to get the community host + return path let redirectUrl = '/'; try { - const statePayload = JSON.parse( - Buffer.from(state, 'base64url').toString(), - ); + const statePayload = JSON.parse(Buffer.from(state, 'base64url').toString()); const host = statePayload.host || ''; const rawReturn = statePayload.returnTo || '/'; // Validate returnTo is a safe relative path - const returnTo = typeof rawReturn === 'string' && rawReturn.startsWith('/') && !rawReturn.startsWith('//') ? rawReturn : '/'; + const returnTo = + typeof rawReturn === 'string' && + rawReturn.startsWith('/') && + !rawReturn.startsWith('//') + ? rawReturn + : '/'; if (host && host !== req.hostname) { // Redirect back to the community the user came from @@ -241,8 +236,7 @@ router.post('/auth/logout', (req: any, res: any) => { router.post('/api/kf/profile-sync', requireInternalKey, async (req: any, res: any) => { try { - const { userId, givenName, familyName, displayName, email, image } = - req.body; + const { userId, givenName, familyName, displayName, email, image } = req.body; if (!userId) { return res.status(400).json({ error: 'userId is required' }); @@ -383,9 +377,7 @@ router.get('/api/kf/billing/usage', requireInternalKey, async (req: any, res: an // Placeholder — just return community count for now return res.json({ kf_org_id, - line_items: [ - { key: 'communities', quantity: communityCount }, - ], + line_items: [{ key: 'communities', quantity: communityCount }], }); } catch (err) { console.error('Billing usage API error:', err); @@ -432,14 +424,13 @@ router.post('/api/kf/transfer-community', async (req: any, res: any) => { const userOrgs = await fetchUserOrgs(req.user.id); const targetOrg = userOrgs.find((o) => o.id === kfOrgId); if (!targetOrg) { - return res.status(403).json({ error: 'You are not a member of the target organization' }); + return res + .status(403) + .json({ error: 'You are not a member of the target organization' }); } // Update the community's kfOrgId - const [updatedCount] = await Community.update( - { kfOrgId }, - { where: { id: communityId } }, - ); + const [updatedCount] = await Community.update({ kfOrgId }, { where: { id: communityId } }); if (updatedCount === 0) { return res.status(404).json({ error: 'Community not found' }); @@ -461,14 +452,33 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, const startDate = req.query.startDate || null; const endDate = req.query.endDate || null; // Determine analytics date range - const analyticsStart = startDate || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); + const analyticsStart = + startDate || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); const analyticsEnd = endDate || new Date().toISOString().slice(0, 10); const pubsMonthsBack = startDate - ? Math.max(Math.ceil((Date.now() - new Date(startDate).getTime()) / (30 * 24 * 60 * 60 * 1000)), 3) || 24 + ? Math.max( + Math.ceil( + (Date.now() - new Date(startDate).getTime()) / (30 * 24 * 60 * 60 * 1000), + ), + 3, + ) || 24 : 24; const community = await Community.findByPk(communityId, { - attributes: ['id', 'title', 'subdomain', 'domain', 'avatar', 'accentColorDark', 'accentColorLight', 'headerLogo', 'heroLogo', 'description', 'heroBackgroundImage', 'heroImage'], + attributes: [ + 'id', + 'title', + 'subdomain', + 'domain', + 'avatar', + 'accentColorDark', + 'accentColorLight', + 'headerLogo', + 'heroLogo', + 'description', + 'heroBackgroundImage', + 'heroImage', + ], }); if (!community) { @@ -501,26 +511,38 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, Pub.count({ where: { communityId }, include: [hasReleaseInclude] }), Member.count({ where: { communityId } }), Collection.count({ where: { communityId } }), - sequelize.query( - `SELECT COUNT(*)::int AS count FROM "Releases" r INNER JOIN "Pubs" p ON r."pubId" = p.id WHERE p."communityId" = :communityId`, - { replacements: { communityId }, type: 'SELECT' as any }, - ).then((rows: any) => rows[0]?.count ?? 0), + sequelize + .query( + `SELECT COUNT(*)::int AS count FROM "Releases" r INNER JOIN "Pubs" p ON r."pubId" = p.id WHERE p."communityId" = :communityId`, + { replacements: { communityId }, type: 'SELECT' as any }, + ) + .then((rows: any) => rows[0]?.count ?? 0), // Members with user details Member.findAll({ where: { communityId }, attributes: ['id', 'userId', 'permissions', 'isOwner', 'createdAt'], - include: [{ - model: User, - as: 'user', - attributes: ['fullName', 'avatar', 'slug'], - }], + include: [ + { + model: User, + as: 'user', + attributes: ['fullName', 'avatar', 'slug'], + }, + ], order: [['createdAt', 'ASC']], limit: 500, }), // Recent pubs (released only) Pub.findAll({ where: { communityId }, - attributes: ['id', 'title', 'slug', 'description', 'avatar', 'customPublishedAt', 'createdAt'], + attributes: [ + 'id', + 'title', + 'slug', + 'description', + 'avatar', + 'customPublishedAt', + 'createdAt', + ], include: [ hasReleaseInclude, { @@ -529,7 +551,9 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, attributes: ['name', 'avatar', 'order', 'isAuthor'], where: { isAuthor: true }, required: false, - include: [{ model: User, as: 'user', attributes: ['fullName', 'avatar', 'slug'] }], + include: [ + { model: User, as: 'user', attributes: ['fullName', 'avatar', 'slug'] }, + ], }, ], order: [['createdAt', 'DESC']], @@ -546,7 +570,12 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, AND p."createdAt" >= :pubsCutoff GROUP BY 1 ORDER BY 1`, { - replacements: { communityId, pubsCutoff: new Date(Date.now() - pubsMonthsBack * 30 * 24 * 60 * 60 * 1000).toISOString() }, + replacements: { + communityId, + pubsCutoff: new Date( + Date.now() - pubsMonthsBack * 30 * 24 * 60 * 60 * 1000, + ).toISOString(), + }, type: 'SELECT' as any, }, ), @@ -621,7 +650,10 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, }); // Aggregate top authors - const authorMap = new Map(); + const authorMap = new Map< + string, + { name: string; avatar: string | null; slug: string | null; count: number } + >(); for (const attr of topAuthorsRaw) { const a = (attr as any).toJSON(); const key = a.userId || `name:${a.name}`; @@ -637,8 +669,7 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, }); } } - const topAuthors = [...authorMap.values()] - .sort((a, b) => b.count - a.count); + const topAuthors = [...authorMap.values()].sort((a, b) => b.count - a.count); // Try to get analytics (daily views for selected range) from matview let dailyViews: Array<{ date: string; views: number }> = []; @@ -732,10 +763,13 @@ router.get('/api/kf/community/:id/detail', requireInternalKey, async (req: any, router.get('/api/kf/suggested-communities', requireInternalKey, async (req: any, res: any) => { try { const domainsParam = req.query.domains as string; - const excludeIds = req.query.excludeIds as string || ''; + const excludeIds = (req.query.excludeIds as string) || ''; if (!domainsParam) return res.json([]); - const domains = domainsParam.split(',').map((d: string) => d.trim().toLowerCase()).filter(Boolean); + const domains = domainsParam + .split(',') + .map((d: string) => d.trim().toLowerCase()) + .filter(Boolean); if (domains.length === 0) return res.json([]); const excludeList = excludeIds.split(',').filter(Boolean); @@ -786,7 +820,10 @@ router.get('/api/kf/suggested-communities', requireInternalKey, async (req: any, communityMap.set(row.communityId, { managerCount: row.managerCount, authorCount: 0 }); } for (const row of authorRows) { - const existing = communityMap.get(row.communityId) || { managerCount: 0, authorCount: 0 }; + const existing = communityMap.get(row.communityId) || { + managerCount: 0, + authorCount: 0, + }; existing.authorCount = row.authorCount; communityMap.set(row.communityId, existing); } @@ -800,7 +837,9 @@ router.get('/api/kf/suggested-communities', requireInternalKey, async (req: any, const communityIds = [...communityMap.keys()]; const idPlaceholders = communityIds.map((_, i) => `:cid${i}`).join(', '); const idReplacements: Record = {}; - communityIds.forEach((id, i) => { idReplacements[`cid${i}`] = id; }); + communityIds.forEach((id, i) => { + idReplacements[`cid${i}`] = id; + }); const communityRows = (await sequelize.query( `SELECT c."id", c."title", c."subdomain", c."domain", c."description", c."heroLogo", c."accentColorDark", c."accentColorLight", c."createdAt", @@ -841,23 +880,36 @@ router.get('/api/kf/suggested-communities', requireInternalKey, async (req: any, router.get('/api/kf/suggested-pubs', requireInternalKey, async (req: any, res: any) => { try { const termsParam = req.query.terms as string; - const excludeCommunityIds = req.query.excludeCommunityIds as string || ''; + const excludeCommunityIds = (req.query.excludeCommunityIds as string) || ''; const limit = Math.min(parseInt(req.query.limit as string, 10) || 50, 200); if (!termsParam) return res.json([]); - const terms = termsParam.split(',').map((t: string) => t.trim()).filter(Boolean); + const terms = termsParam + .split(',') + .map((t: string) => t.trim()) + .filter(Boolean); if (terms.length === 0) return res.json([]); const excludeList = excludeCommunityIds.split(',').filter(Boolean); // Build tsquery from terms — use adjacency operator (<->) for exact phrase matching // e.g. "Mellon Foundation" → "mellon <-> foundation", single words get prefix match - const tsQuery = terms.map((t) => { - const words = t.trim().toLowerCase().replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim().split(/\s+/).filter(Boolean); - if (words.length === 0) return null; - if (words.length === 1) return `${words[0]}:*`; - return `(${words.join(' <-> ')})`; - }).filter(Boolean).join(' | '); + const tsQuery = terms + .map((t) => { + const words = t + .trim() + .toLowerCase() + .replace(/[^\w\s]/g, ' ') + .replace(/\s+/g, ' ') + .trim() + .split(/\s+/) + .filter(Boolean); + if (words.length === 0) return null; + if (words.length === 1) return `${words[0]}:*`; + return `(${words.join(' <-> ')})`; + }) + .filter(Boolean) + .join(' | '); if (!tsQuery) return res.json([]); @@ -865,7 +917,9 @@ router.get('/api/kf/suggested-pubs', requireInternalKey, async (req: any, res: a const replacements: Record = { tsQuery, limit }; if (excludeList.length > 0) { const excludePlaceholders = excludeList.map((_, i) => `:excl${i}`).join(', '); - excludeList.forEach((id, i) => { replacements[`excl${i}`] = id; }); + excludeList.forEach((id, i) => { + replacements[`excl${i}`] = id; + }); excludeClause = `AND p."communityId" NOT IN (${excludePlaceholders})`; } @@ -903,21 +957,23 @@ router.get('/api/kf/suggested-pubs', requireInternalKey, async (req: any, res: a { replacements, type: 'SELECT' as any }, )) as any[]; - return res.json(rows.map((r: any) => ({ - id: r.id, - title: r.title, - slug: r.slug, - description: r.description, - avatar: r.avatar, - communityId: r.communityId, - communityTitle: r.communityTitle, - communitySubdomain: r.communitySubdomain, - communityDomain: r.communityDomain, - byline: r.byline ?? null, - snippet: r.snippet ?? null, - publishedAt: r.customPublishedAt ?? null, - rank: parseFloat(r.rank), - }))); + return res.json( + rows.map((r: any) => ({ + id: r.id, + title: r.title, + slug: r.slug, + description: r.description, + avatar: r.avatar, + communityId: r.communityId, + communityTitle: r.communityTitle, + communitySubdomain: r.communitySubdomain, + communityDomain: r.communityDomain, + byline: r.byline ?? null, + snippet: r.snippet ?? null, + publishedAt: r.customPublishedAt ?? null, + rank: parseFloat(r.rank), + })), + ); } catch (err) { console.error('Suggested pubs API error:', err); return res.status(500).json({ error: 'Internal error' }); @@ -936,7 +992,9 @@ router.get('/api/kf/graph-data', requireInternalKey, async (req: any, res: any) const idPlaceholders = communityIds.map((_: string, i: number) => `:cid${i}`).join(', '); const replacements: Record = {}; - communityIds.forEach((id: string, i: number) => { replacements[`cid${i}`] = id; }); + communityIds.forEach((id: string, i: number) => { + replacements[`cid${i}`] = id; + }); // Get communities const communities = (await sequelize.query( @@ -980,7 +1038,13 @@ router.get('/api/kf/graph-data', requireInternalKey, async (req: any, res: any) })) as any[]; // Build graph nodes and links - type GraphNode = { id: string; label: string; type: 'community' | 'person'; color?: string; avatar?: string }; + type GraphNode = { + id: string; + label: string; + type: 'community' | 'person'; + color?: string; + avatar?: string; + }; type GraphLink = { source: string; target: string; roles: string[] }; const nodes: GraphNode[] = [ diff --git a/server/kf/auth.ts b/server/kf/auth.ts index 8e171f533..850bc028d 100644 --- a/server/kf/auth.ts +++ b/server/kf/auth.ts @@ -62,10 +62,7 @@ interface TokenResponse { refresh_token?: string; } -export async function exchangeCode( - code: string, - codeVerifier: string, -): Promise { +export async function exchangeCode(code: string, codeVerifier: string): Promise { const body = new URLSearchParams({ grant_type: 'authorization_code', code, @@ -125,18 +122,13 @@ export async function fetchUserInfo(accessToken: string): Promise { * Fetch a user's current KF orgs from KF Auth's internal API. * Used for the ownership picker when creating communities. */ -export async function fetchUserOrgs( - userId: string, -): Promise { +export async function fetchUserOrgs(userId: string): Promise { const key = process.env.KF_INTERNAL_API_KEY; if (!key) return []; - const res = await fetch( - `${KF_AUTH_URL}/api/internal/users/${userId}/orgs`, - { - headers: { Authorization: `Bearer ${key}` }, - }, - ); + const res = await fetch(`${KF_AUTH_URL}/api/internal/users/${userId}/orgs`, { + headers: { Authorization: `Bearer ${key}` }, + }); if (!res.ok) return []; const data = (await res.json()) as { orgs?: KFOrg[] }; diff --git a/server/routes/index.ts b/server/routes/index.ts index 86cd64c24..a82692dec 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -1,5 +1,7 @@ import { Router } from 'express'; +// KF Auth integration (OIDC + internal API) +import { router as kfAuthRouter } from '../kf/api'; /* import { router as picingRouter} from './picing'); // Route: '/pricing' */ import { router as adminDashboardRouter } from './adminDashboard'; // Route: '/admin' (redirect to superadmin) import { router as authenticateRouter } from './authenticate'; // Route: '/auth' @@ -7,9 +9,6 @@ import { router as collectionRouter } from './collection'; // Route: /collection /* Routes for PubPub */ import { router as communityCreateRouter } from './communityCreate'; // Route: '/community/create' import { router as dashboardActivityRouter } from './dashboardActivity'; - -// KF Auth integration (OIDC + internal API) -import { router as kfAuthRouter } from '../kf/api'; import { router as dashboardCollectionLayoutRouter } from './dashboardCollectionLayout'; import { router as dashboardCollectionOverviewRouter } from './dashboardCollectionOverview'; import { router as dashboardCommunityOverviewRouter } from './dashboardCommunityOverview'; diff --git a/server/routes/signup.kf.tsx b/server/routes/signup.kf.tsx index ac7f59e06..cc173f962 100644 --- a/server/routes/signup.kf.tsx +++ b/server/routes/signup.kf.tsx @@ -7,7 +7,7 @@ import { Router } from 'express'; -import { KF_AUTH_URL, KF_AUTH_CLIENT_ID, APP_URL } from 'server/kf/auth'; +import { APP_URL, KF_AUTH_CLIENT_ID, KF_AUTH_URL } from 'server/kf/auth'; export const router = Router(); From 22746d218edaa7ba2eff5571df6052bdef5bb529 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sat, 16 May 2026 23:02:18 -0400 Subject: [PATCH 05/16] Add index --- server/community/model.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/community/model.ts b/server/community/model.ts index 26cd2cd81..83e562e97 100644 --- a/server/community/model.ts +++ b/server/community/model.ts @@ -18,6 +18,7 @@ import { DefaultScope, ForeignKey, HasMany, + Index, Is, IsLowercase, Length, @@ -242,6 +243,7 @@ export class Community extends Model< declare templateId: string | null; /** KF Auth organization that owns this community (for billing/ownership) */ + @Index @Column(DataType.TEXT) declare kfOrgId: string | null; From d1eecc354a830fade9bd00b4153089f5499c63a7 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sat, 16 May 2026 23:23:44 -0400 Subject: [PATCH 06/16] fix http --- infra/.env.dev.enc | 102 ++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/infra/.env.dev.enc b/infra/.env.dev.enc index eb0c10569..56a947e43 100644 --- a/infra/.env.dev.enc +++ b/infra/.env.dev.enc @@ -1,59 +1,59 @@ -AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:Xja6pWJuVFV+Y/aYvSB6pVk5sRgYhGtz38T4XlESV2U3BicdxOzJiblI64hp9ptivW434emiC2CRBQEoY2iGIg==,iv:BkfH04v7u+nQJ5C6oSsbvG7qfclnq4WUvNZJ5LvbcYg=,tag:y23Bn78rh6p0BXzvDrGvpA==,type:str] -ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:gM4u13AU5rGlfvuK1TygB2EHhXgNuM/ymNkYHnUH7F6ibMwIMANf3DPCxKRfAD+Hg6bwFoziroxz93bw7CeSdQ==,iv:BV0GSx28moDNPmGd3Nr0+tl2Kap4Fcn/5P+sNZmmTPY=,tag:Kug/H8MvvINSBESdeVLJmA==,type:str] -AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:vF++ggIsvfjqhnVOXWFAVunjsVY=,iv:LjsUFh31LTLn8LLjVLFPBiqFnbEZutKQZl59yInZXwg=,tag:bM53fg0cur5DoT0OrQGMlA==,type:str] -AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:3XOOm6S08GV3Srzpq4YUarG9wRA=,iv:peWv8G0v/2oc5pBczqP6gMFDM5xAiqQcHTQdUOrtzhg=,tag:St5ojbZCyyluwEGTOt3xxg==,type:str] -AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:1RGXw9lVjLekUyIheth5Hp7ieyOP2J1tgUL1gr9xKP7i9kRS2wCrjw==,iv:kaPbn3r8bCb3Kx4rG512hi5SkopmGI0avvKLbMtfXiE=,tag:vLmLBh3eLyQeHQRoiJcaow==,type:str] -AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:Pdrg+Cny7TbtBD1h3b++V2vqL7jQCDWL6KqMNMHn3ol/0iFyy1gTUg==,iv:b/rit8FMdaV3zByD22TdcnrZ6qHSVjzDYOGvERO8SiQ=,tag:eSqtBcFUuhCcw/6PaLJf0A==,type:str] -BACKUPS_SECRET=ENC[AES256_GCM,data:jhMNkb/JM5C6c9OC+ML0a1vFd1y0ngo9HkSeCmYEKLR8RHal1Xtra49vDHU=,iv:FoJbGu/srmg8hK2evv6QZvBNih2dvG72PIxSZi5ieUA=,tag:T/CEkRDshX53EdWLZhHe1g==,type:str] -CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:YG3oxF79I2Op7m74ioI6fU7qndkls3XZa409iyPe3TgsAb8oNk0sb0UM2pPWuvQd0/lXxiY=,iv:QHQlzlUahFh/BwCP/Qt86fEP6PTZlxCyMNsa1kBajcI=,tag:3YcBpFEVj/rXLjjEh+kkkQ==,type:str] -CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:xhEGXVdEz6AseRerwAXQSZp7G/USThj89U9OMHlNq2pfG8nekHDgoyDQ0cS5kkin6JSKpP0=,iv:YMXXosY3TOjIf7QKo6bd7Soqa4S0ojliNpwn9gVdTFc=,tag:DAn2bpuZg4rnkiAikBa5Rw==,type:str] -CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:7W8jZBTLIMYB6m593J5q7Xs+Zd9fvnn/loX3thddkcU=,iv:lUJwLq4D/eus8rGxC26cEaioVCNOu7RPLQFizR77Qtw=,tag:MtTY3QbPePlPa262w7arpw==,type:str] -CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:bppDCBSkEcnBiCMbq4vRCIHcllN92JPPWw/lVg9pk0w=,tag:0LG4zm0f2qYRkmpwjEY5gA==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:AzarkHcGAEqaIy5jF6fuFGBBUvOqtJwsURw5eQjB73jjsu9edl9kJuJniYs=,iv:nMVI5p/YvwG2ui9Io/0SmkiAUQVYsafgejmKP54eg1k=,tag:Ckzfcv5Bqb9nWSsO/4Zl/g==,type:str] -DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:RtWHvl9rL7czXLByOpD6TMrWV9hWcfl07F9k9S3frHTdcg==,iv:4FgMHgbL7meB7xsoP0ZgDqHzmAYdotGMwB5rjQz0+4w=,tag:v3qVTk944kaCFrSElDLl9g==,type:str] -DOI_LOGIN_ID=ENC[AES256_GCM,data:eJXP310Y,iv:hHm0RYxErafZsNWIp4c3GMkGlwD3WiHIYi1dqDf4MY8=,tag:roFFgJRFOSCp7GLTAqW9Nw==,type:str] -DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:H3gIlkMt/TFB68Qnahf027i3CPM=,iv:zF5rBVnU7mooeiMvxfoCVv9VyNaroy0WvGbukhO53vM=,tag:NL+GYVrWtF7m0/q7kMnduw==,type:str] -DOI_SUBMISSION_URL=ENC[AES256_GCM,data:e10KvblYLyoovn0wTiD7n1COW3codFR+PRD9VcW3VREgkM63SVQM2zE=,iv:L5MWiPcqkIYq3J7rodg3Z0dMmrOZazcKgJwACjl2MgE=,tag:2/5NDyx2uqypSPd+tE0wqQ==,type:str] -FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:F2/m+nPABzbg1IIOu9q29+MsQxIlUHAReMmrIp2u8oE=,iv:T1mSVzxGt3yrVifSsP30KKyufXAQxavCeAl7u1g4tsg=,tag:f9aRwYgw67Ti2UuGBWftCA==,type:str] -FASTLY_SERVICE_ID=ENC[AES256_GCM,data:aSOOgoDfcVnm08avr+zcCLkiO2KP5g==,iv:TExbbOCbHwy1zARIMQA8zIkrMuqrvWMDdq0lob1E2ZE=,tag:aYa5JaThXA1AJdGyiOj6NA==,type:str] -FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:/jadVet/Hz82+n/ApBYIa7KUGQ+IwEWf6Y7+m9h3mnI=,tag:5I7IMYbVqBRdTHkbXefM+g==,type:str] -IS_DUQDUQ=ENC[AES256_GCM,data:/Nx7Hw==,iv:uDInaGYuPtRuTFP4sb6NBPVEwoYKV4/Cqy4Vi7ePuHk=,tag:e3+FWrP0odei40mpHRQRSQ==,type:str] -JWT_SIGNING_SECRET=ENC[AES256_GCM,data:vAGHcGOpR52+MOVWM2e0g5+FVYpeNIq6dnyEwEAn8EDP+iCi+nKI7vGNWL3MYoD9RRFCpDiOQiPPc8NVF//Wzxg/jrCvEhpM1cXYJvdSlrR1wsxAFpEzBUemwbQhOMlcml5wAxQfajxGdsK284QabHc+DLz5eO7KY/549L6vg5xCu7JdMPhY7pjEyZuK8/n8vRGeZ41zipCuztszn0USF0yLbQVr1VpujszMRnKnKjKhL6QVJLC9zSC0jiiKM+/NwrviINRWDRkSPPWENTwx6d/P7zsQ8h3zY5JyCEZ+8O/YHG54KuFAS8JK3ZfM6ttyP6Mv/fRbeDeeK8S7Etdtu89R/n6m+itdd3wz7LDjgn3WsTLhoMrsc2i5vpld6NwhIVoQ5N7Lc2qjhkTUZdSF2WUTCKjtQb2TV2IlnQUGcwIC8SJUPeJY3mVlALnX9gOY1IIeIzWUUs0CO8cFl6lsWnTTU/EtiF4g40G9m0w6B07CAnvClpH2gHNl/+MHMR/Gu4VS7cb7NajToamNeg/s7POTGcl4RYxWpJUXMSWoDP0DPHzY46WqmtqPRfWIvrl8fYUneUfqDpTqGtp2NBzauvbqKxRqQzERE3W5PK65hDzY508Rgf1ybQVnH7/BDiS49UJpzoZ08d8/JR94Q3L9UiBsCweiUdgqWSamvQI8uAy945K59lPKsFT2MNo9nMXnTaAuym0T14kk+eMrNZnE4Zi7vW1k1RxyBobVIBRAICx+ZgcPWesimbu7xeG5QyX0VvWwpMdgF93dULDwGTNtPyzx/wFXgv7YVzTi1ssNMd1hjIkp15lxIq62poNpaO7N2ylcHlrbXMlKwT+bB//WxJg6MBtlHmuC5yH6MzKu2Ffr7BnEcwevH+mmssbbGxkXasAZJpFtJ+BbO6aQMHHz7aN4TsxG/wHD2CLSsMVFNHRo55g1ZT6NNok/uGQLocexlu046Abjd6LArf9t+1U56Td8fguLYZ5t5JnjrpePsRzJzX7h5fRUH/5V/kQOJAiqZ6iB5MomV6tVN+uGUDrOgqfWsG/LH4RkBMtyT3mNEKtJfWLKvA1lO8S9YBB94YJ/FSFMyAeriDg5n8XrlE0z+I1JN3exGRB0+c+Oq0MlWfoUb05v3bpMUWqKmgP1Ata/DCyOyKrZEXhwFY+no3i6bf5j57gmBxxECjv0fyqvJM3AViVKQ9OvyhQoyCYEOVPNfc5ZBogc38pItrzL5a6hB5W4Z7MBeyxhvOe2vaoCPsR4qP1It5K2+3bmtEX4KXTy7tXtkQ4xCg/rvpOeN7ZjnEt3r3MaZEvKgpCWbQAF3JRzFKVb2mtVC5z/lqeffCvNhJN7XP/ztdvrFQNfDEIzjA==,iv:BnISOMJ3PNy/6165fq9oeFykKx666yHzuq0UQkaiZlI=,tag:zkhJAgY0EvR+PmOZLNwOEg==,type:str] -MAILCHIMP_API_KEY=ENC[AES256_GCM,data:NtsS50tkEddOu5T9H2spXFd749cAEsnQZgEh3tgBjRWK+CiU,iv:7MpAPJzuVnaIdHYeHmEN1l6xGgw3+fvUvRKJEX6Kid4=,tag:tW1bBQM0GF6BqbkaY7USsQ==,type:str] -MAILGUN_API_KEY=ENC[AES256_GCM,data:MH6seKkqHR6Wj9JZH4mVmWHF4R3FrKsTat2W+r9tQPVByFIZ,iv:YjlSJ/VbJ09RPs3txF7gs5PEut19/QTn/c2zRZUUxFg=,tag:HikxvDvrLHUuvfPIFJApiw==,type:str] -NODE_ENV=ENC[AES256_GCM,data:eXjT6u9VZktBhw==,iv:y7svJfKogEh+/dxAxH+HLd6mAQyb+eGzjgbuxOkT9fw=,tag:yYUGMmBStX9EnWKgKVgk6w==,type:str] -S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:eYFRgCAIWsY8Shhr2HZHCR72r7c=,iv:5M5nftqJi1le1sDZz8YZa32QwqrCxOu5ClZ7knWvQQg=,tag:hE65DX9ap9wCXeisDc54HQ==,type:str] -S3_BACKUP_BUCKET=ENC[AES256_GCM,data:x1ZgjjdMObQ9WFg=,iv:VqTy+9ZCg3jzgU9CtYxGVgLLzGfMnmhqmCHB8dGIo/Y=,tag:kSWhM9gxKQK1jf/XX3mT8g==,type:str] -S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:UVnBLUILntsJ+ledQ6auXqP53cAFaDerWgsdGOgOZ84ZyrQ=,iv:iXlsZg56X7yD6Yu9xOvUnSL0kYi3pxyLYTQOhUtv3ow=,tag:QZG374UhNDOw4I4rc4fI1w==,type:str] -S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:6c30LpSh1ix7R2GDNCKcenjpEaZ0TuFmzGhiuoOtdYkXYQO7X98jag==,iv:8c9sE/sNIwnaJtdufSOLruvN9tHVUN0B0dVYLvoi3/A=,tag:h2JN5JtLI6B5Jn7/m6QpGg==,type:str] -SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:P8D2K73O9Oc+uPMwIgtB942HOIjvpNSIl/pit7TPpJgM1HW7LMSUf/4BCM0Iu269p6lehrUpf5ZcSIL6yCnotfXRZFY4G0syqXvKGvqiTpklVttg+kjsOFxsHEWBe0vRvnSfyUfqVmrfWAh+83qu6kLrwNlQOrE4FZm7sPijZ6H3AW3KMLqNm0i3zKDgDT1vLEBxc6o9M09MUauHsE5l3qzeQe3kFruxhqk11lOaZIOL+ZjnrDWYbjxNlQ==,iv:XfwOlinRFKp5OKtWeFID/inAWGqMfbReHwLuuaFMH0I=,tag:BPKr/6ghaqka7xYpSn8t5A==,type:str] -SENTRY_ORG=ENC[AES256_GCM,data:adqt,iv:ujBPuKnFCHJYOjTs4gyvdNCSumNo92AzFVAhF6q1cu4=,tag:5iWhjeGmU9KghzzBlYZLbA==,type:str] -SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:q7Y=,iv:S5SKSAzNZJAVbIthcYuqhloyQTvCp6us2gA2qAQKW+M=,tag:mwwPbkoC0pzswsO5iDN6LA==,type:str] -SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:GNmQUBEwYvDbHVMbKs1SKlUa9dLDOr9e1iEdQMR4P0C+pQw2CohRVarg95gL+/AbE2mxRBg4d2v//L0zs4v1SqPT35wOOmNICnzX1py7wQ==,iv:AsMZhFlFr4eD2j2wfrQQD2l/fNxtFxqAtln7Enu/xQ0=,tag:prqSzsxXMjqc4KF3Q3xIAg==,type:str] -SMTP_HOST=ENC[AES256_GCM,data:HSZEWRhNJCx8qsxwo+RKZK6JlbPbjyqOcYhSqiixouIp8A==,iv:u5uSGvmkVfpY8kggb9lhfj02nkGH51KQv9VJCJ2WYMg=,tag:5i2VVrAltQxY2sfH1lAOqQ==,type:str] -SMTP_PASS=ENC[AES256_GCM,data:/LbUwUWza4AygxRJBxV/IytK+v+WCdmIV/XnuhsIBrhI6PtIXwtD1s/7c38=,iv:lI0ZV/pqFg2x01LwtFx01T1Ji3M7eRLPUViSOu9WCSc=,tag:UTZ40VeCJc4QuG20E1WJ/g==,type:str] -SMTP_USER=ENC[AES256_GCM,data:nbkdNZYxZWwVxfliNb2hj3+2mEM=,iv:m8om1mOpz3XdWoFq40JrUJJWXMUWuDZNhLcTjp4zCwY=,tag:jE5smruSBVenqp7QCqBYow==,type:str] -ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:QrW07ag3c1b2MJEkvflkYcZ9Jic=,iv:PTjKpT/HGxgUM0frWG9jLYzyXnyZUx/mwn1a87BNij8=,tag:afxyr2HJADre/rORYA8TOQ==,type:str] -ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:rTqa6mWSJSteDn2HkkpAeKjTRvA=,iv:R9wUAudkXbs9PqO/gXbx8WUihWoV0li/buHhFL0lvQw=,tag:9XDZyDaPzK7SYRO3fp6tww==,type:str] -#ENC[AES256_GCM,data:SjEixq+m46e8vo26CpWPqTcFYSs=,iv:+ksMsnLErAhThWqJa88IP7lowNiDAZG4aUCFMcy+7Pk=,tag:wuFJe+nn7Tsl9VWQtn/evw==,type:comment] -KF_AUTH_URL=ENC[AES256_GCM,data:PQaNOYW1ZSe5hy+2xVnRSe9mPCOe4IN/LuIdhIk/UZSBJ4sd,iv:Pd5ZQGOqEBcqPCi1t1qZQ5qnF8Qcqnteljg6b4tKhwY=,tag:l5OJIQnh8vruOuboIjN5BA==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:+59Jiev83Fof,iv:hcD8yUMGkiKD22hGFfWvpz1fL6tyAB9yLKVE8N18Xr8=,tag:DQRVc+FYeyXC4dvW81Ml3A==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:CluRm400lyJf10aNLoJAMWH6xADMBP7DivlinSdqWMeqdU7/ImpDbieNw4adJD9/6pDX9EGRTjEEg88zABiCSQ==,iv:tlwGBDQFPt1Xvl3FHAQb/M60v2/J0QN+QKf71pO31SA=,tag:2nNTBSLXUZ+Bvwd0uVxtBQ==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:NaWwUjdSLWwZmC0kO3Y7o3TRtwZQm/4Nwiz013um8g9GowbnNTGYwvkOjXQZ5PiMnfn+PaswSDGKR3+W/0OE1Q==,iv:UqeEm1DvkVThTIoY/igLRGngaSAHjwixEIYmdm8fQG0=,tag:QxEbPAQCj2z/tCZ6rgsItg==,type:str] -APP_URL=ENC[AES256_GCM,data:hjPOso+xBu5uNa9Tda+GbDG0qQxB,iv:a+E73avCFL7xmuztLpuFHLah7GQOwtJpO6ljsTW4ong=,tag:2WdXA3bH21ajwzs9ga/Tuw==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBta2RjbVBPN0I5ZUFqSGdU\nTGlSV3NERmg3ZFJPZk5CM3BDRk1TN3d3bUE4Ck1yVTFZTisvcGVYczdrSnhqWm1n\nbU1peWtsSUQ2MmpiK3ZFS2NmUlFNTE0KLS0tIFFtUXlkSTg5VFJFdWtZNDZBVXUy\nNGx3R1BtdWtTcm1wTlkwcFVVcUZMbVkKxYYUSSeUDsK4TePTTQzItWrBJEMoWmOj\nLkITwYDxj2YlLnMC4129N732nNaBFLy2Aruc3XaivO8Fr6eCtvRF7g==\n-----END AGE ENCRYPTED FILE-----\n +AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:0/pThcyBgD71JouwgwyrGv4WpahqQdOOLmttTM86s+YGgimz70ah/MFhPdsDFJJWumb/TPWxX/VeP6NsbfV9FQ==,iv:6MDo+RbTa8NBQgC0fCWYeTy6BJV2A2rQHdDjUEn02N0=,tag:4kV5AVKtr7LlN9sWWxXBlA==,type:str] +ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:68lcVheah2pt9Nbir4yaUq6ocmDBd/BEY1P3DQQc6mNMjYIEhXnA5SB/DEismNpCL3VCQWNylDVHXsILJLvOxQ==,iv:tDx7V9M3ci7t8buYjvMhkfJq+wFiEgKzLGP2Dg4mHS8=,tag:cblmvhpbTd2mU30oIQ4gKQ==,type:str] +AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:+a//C1kWWbLomU2p7rYX5u4rkAs=,iv:oIoKmSZ2yTY8mndEOmyqBfAdafoIDH/8LKkG8kABQMw=,tag:nGjdlaF1l34lxqOWZpAHPg==,type:str] +AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:NtmyfFACf+Xu1AvD9loJp035oio=,iv:lz0uz2ycbWhkabM0IF+NJ5HhlelkZ/eNfK8sLW7dVBw=,tag:QBkvDiEFbvEMYDzP0JjRBA==,type:str] +AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:AX8joyH10Ub+N+NDAyLpH2EedX9mE3A/6TNKOe+P86g6c6OnWAnPaQ==,iv:dR5DYNDvwZU+n8k/Lr6xpOUiXc3QQ2kIX6q3Tqspva8=,tag:MyYLKYDJZ3fhacH92Q794w==,type:str] +AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:QXj9kz2WoScLvEj/3IkatJlbicQpSAwU7E86jV2icbSOys1iigzKrQ==,iv:lCLu4jzI7XqrIwxcwlcMdBpkwc41R4e+i0CiH/ZPUC0=,tag:16fNUIHWScxkhB6Gh5soew==,type:str] +BACKUPS_SECRET=ENC[AES256_GCM,data:5XW1JuOwA6QMhFDb3HAsC3VvAAfRrpZJtyo3MfywxF9/xLfSsB1qtSA8kkQ=,iv:treasNyESOYrObwki6S/3VtaedqGEeuWiwtRFqHJhpo=,tag:XsWwzPgkm5xs4sZ3Ao2U8A==,type:str] +CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:rQ1Mn3NjQ4Uz0ThThWZQcfsdvYF5w/oijEiISguRXmQ5dSXfgRPJZNk7THkiRCt/WQpmOpw=,iv:Fkl134fgT0Y1js7NJAg2DiMVHO5EIjLP+4e35nva/ls=,tag:RRJ9XeCFRioJSopuWO+5Bw==,type:str] +CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:MbQ0eYpUzwqsF0/JQrNI0ifrwKxdqFjkAouuq5q2AZFyKkkDeQ7PbyunOcbnxyM9BXajekg=,iv:MT5nY39XTrdo06Qg8R2zuwX/Pj8cIQ2BF1HrL1HWg3o=,tag:3ZsabAWhA4mNvJDqApf+MA==,type:str] +CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:T0eYzCGVinoAShLqgPsW85b4gFBBxnLE0PmS5RKdUks=,iv:n+V8Ct0R4Mw1D22/YEwjB91eLnoIro3/UjyEg6dIhmw=,tag:IV7PLYkwa9RgGO98vZkUSw==,type:str] +CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:hdYZ5t2pU2BtIF7KSkOgdq7S3myDPI9Hx4p4fqTY++Y=,tag:YV0Jdn3cGAXnfDFe4vu2sA==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:GyGXrI48jnJ86TcdsZGucGtPcbMgd2nKh8+FxNd3MEfVpbXw4k7v/OQv5I8=,iv:B8PzKy2Vs3G0VPtexyGTZcJPobgVsAi+tgz3zYjIjc8=,tag:Wnu/vP2aY19efDSdm973rw==,type:str] +DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:1MwvuSNog94kdsVmWzkdmVg+aabVMNPT/o5I2HBL11SBSA==,iv:ocvKFMH+WNParAfV07UP0DDkRC4CCW00CIkRhP+IU+A=,tag:eerYbjt0k/mPUm9s42WYwg==,type:str] +DOI_LOGIN_ID=ENC[AES256_GCM,data:Ocyz8s1k,iv:vut+asYLKbSnxHY23yqIb2eBuYpE5JqGa/ZUvxJwysE=,tag:8i4+DdWNu+f/Mi740d8rYg==,type:str] +DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:Q3Z1IamNafwJ2r94e0qccqbGJ4U=,iv:ezsIfdXsL1zrcai+pYrdCifKP2fbaUo6/mj+ZGsRLKk=,tag:MR5KmCPlT6JDcflnhWk5bw==,type:str] +DOI_SUBMISSION_URL=ENC[AES256_GCM,data:pi+uH+cznIJP80v1aOG2MLj3o3V+blM4cMH6kaNL3hEeipfInMAMf+A=,iv:0HA7zy8zJ5Fjvg3YyUWwE5d9pe1d4J21qU1vPW0whn0=,tag:1K8N8x1g263FL/4JO5oOfQ==,type:str] +FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:KiEKh/s3uRZmtaoXNNpihnm4bj9wAC0PeLbk1U2V1uY=,iv:UWWrQYLqVucyts0yiNFoP/GksRKboSLHfV3N4xv6wiM=,tag:N1d1eoA8rW5Sjlt+sXzjHQ==,type:str] +FASTLY_SERVICE_ID=ENC[AES256_GCM,data:M1VmWapcad3acXAUX2Hibm0IdjXG9w==,iv:0bUgfSHGlAKVA1pXjdNdIy0ZlZ3wR5TyWPrGolVVcPs=,tag:rHRkh3P2gISFlNooVt/kDg==,type:str] +FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:LVt3Bb5jVgVy6ham5LyIBcqxyfD9x6GtYgXfrqkEktM=,tag:0e4k1QJjf8Mp1PVplP+uOw==,type:str] +IS_DUQDUQ=ENC[AES256_GCM,data:u2MRZg==,iv:PO7Ra8bZIwtV9/1XZc4XV0oh4uPxmnVya/PiHpm5mq4=,tag:4etbO8o2OfJhkL+g1lt8oQ==,type:str] +JWT_SIGNING_SECRET=ENC[AES256_GCM,data:GRq9iWaZffs7+ROtfQ4M81nNXktRXYwNs9UPzWkZ4OgE7y7WqUA2ZvVIb4wMecfgAnQkqC0tydnOyhQ3P1V9W/tSov/4p/KLZ8wTNV5wuiUk7rIJ2UNkpR5QWZUavTm9QYVKEkdAq5MvJJi1ByJJ2MLclOMX8wB87l5+X6NWwwv935Oo9+148ikwhjvs5IRgy6exaJsktdNCZqGTP1U3Noz1j/3onaOTGH/rGJTPNj4AjBfWZDJF5qlvVIcvLqwBXI4dp9yicQErhafRCa+SOGzvphdvzf9VV2vq9IwQoBL1NnyIVttTdbbcx9CHDDZljSBxcQixHCxw8lVbakEhY3lb9ARiZcxh2tFkrdorrxeNPhwUtsTjkf8U7S/WTJnSsp+sqdaMMjbhWm0WJxrZ/wF0dj60jfxp5kT3EjpVqnjezTRkeSvn+EyMvH5R0SGQABeXaMzWE1dgUcVL2wCymtjw+sluxo+J+kxHhsFADaj6sP94pLBdoN2cjg5jAgtsfgPYNvS3P4Jy6OBDaQd3YLfNcoFbmP4jMGVb6O7hx75jixe+oGSYIRskk2k02b6i4rtQznS7kqYDGVD4L85QgNIQew4XMPVT7jc/gWC28FNt/MuhinaiMt2lQSlNYvCsm4YcDuTMZXZjbMIBUXUVrLhAhMkyTjZQ9ZzBSSX7pufbkYy5TqUfejPKD2F+Bh91zrFntNdxMNbleJb581PQuQEPkmVTe/FENnOlQKlua70BsSqegVbff0y+uU4Z8H7tYGDgo9TqiPQPbtNSEjloby6adeZ5dd+lyL6a+b3kmVncPzHwdb+5pVTzm1+UOrarMmX4qXFCvCNJiSsJN3bUf544frCHcacCujVfKCVekJT7VrSEv79M2aei8D0ysRCK/Sb7o9P99zrcppYdykvprKKJHhE7ZndICUdx9kxR3+VgMySgTLxFjPljIezKIzbLlrYrYiNQv7/pJMuOYlKX1eLhKdQO8MSt8H1Ue4dB8GUMpj4DRfB9fMhVmXsi2HivzVxksXrSyDC/ifLCJZstP2P2Y/My1HhWQuqE3t+VSE8699k5RpmaXzKxy7qzxulQot7r/hendOkWbIHnezP70nBZU/lqnszgGoMSg0PsZmtuas1e6si9L4hMXYCtnXA01j1y0tbGHzlj9A+5h7wuhIVJb3jIaBaOXgHQbcK0bvh/OTOv5eH4+lgW3w6I1AY7PpH2u0+GxJXP3Ro8BRGvoMTf5vTC7goF8thbZ5nMAPVh4MSDFbOaNKjJoZ2pSQYXJO5xBZHF8cqErNXCPuf9FZIOh4E39scfBe+LFv3cv+R0MZGwMNB1lL2fvPQJ4oyuesSubmEHhUKlSrk1Zcm98w==,iv:9fRAfd2+6XMI0PMZxDr4mp1ThSQEucVkql2OVxD2R3E=,tag:jI6s2ypxFiaBmjPVrdIO5A==,type:str] +MAILCHIMP_API_KEY=ENC[AES256_GCM,data:jF6R33ZGlXhAFAtOI0oYSLXqLFNKtZmACvIn0QJDe/PjsShT,iv:uVPL04rmnBS1ahAiWSWtXnLkijxFQ6jgSux92cO7M0g=,tag:Tq0uTnH1PWgXWnod19u21Q==,type:str] +MAILGUN_API_KEY=ENC[AES256_GCM,data:8PZlwqWZuejs3bCd8CUQAfISkNDdNl57Uj/ZpRSuSDcnFLAU,iv:63ToWd6DHBEB0DsFyd5sZiqJJTdrOlqab+gwtZl9/JQ=,tag:hkYg6BdJy4uf2T7ewTVX6Q==,type:str] +NODE_ENV=ENC[AES256_GCM,data:hlr1cwV1vwwpwg==,iv:NZV8TgQe1SAIjBmysz/d2w+eyKI4kC8nWLMtZw9Kssw=,tag:6AOb8S6Dv8vyx0caYojDCw==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:vyKU23+grCh8wJWiDWEZ9eVWl6E=,iv:mz3oPKUjTBicxWDJ0BhssfyYDp31dcMxPZh8uzwGiXM=,tag:O2L4/z13JUrikRdWDshCow==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:n41bjhL7Yvzd91w=,iv:ZUMCmYQe/Uf5ioA8/knN0B1B38feD36V074uaQnS5Ns=,tag:i5pQHk7PC8moP+jt1wEebA==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:xZB7IA8mLlx9Yewnfc6hN533HlONjtO0Y8bZ5IfJO65PWPo=,iv:j5ktjZUdrQ8G7xezU52WhQPH+Lz7b8HtvYFFvOM7rBQ=,tag:qOr3T14vn3fAuJU/i7LQOA==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:6xpkj2ofmkrrfih1TlxCUIsvtGoDYYFKbmvx+RBtx+80PVDZAcgGaw==,iv:hVppfnf0JHUhhX2U6pCZeBa+Ps/zLmhhcflLv36ZwUQ=,tag:m9vYdOnPya+0BVpu9s5isQ==,type:str] +SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:z6ipQkDdq9GbJxR72urZ8mIzLUKg7Q6DUmhpPtwr1on4gm0RpNRx6Z8pl44O1zEW2uE8EyHAoGMUmfIQa9B4W246S6xC8rqYVDrwyeQpdU64UiPW1UGokei33XcfMVhsKXcXcmbs87p25+gzn+UsV4ofu/rKH/Qls+Wdrsg2vNdaUCrmJKnsDDESfEBs4ym4r1BaByRke6hiKUYZObv+4s62zh01sUtVz1AlVDsG2bZP//GrEaCqRWM+DQ==,iv:WRAMvRFdKMvtsS+uy/fRbwdW71nI9kUt8szDMQCgJeQ=,tag:tcqnU2rdhJAS7Nyw5UwIog==,type:str] +SENTRY_ORG=ENC[AES256_GCM,data:W1Oi,iv:n9GFTs+0EwIA/gCdrgRnlXito1Zvy6i0hdbOzOU/LPc=,tag:C0zE1Awo82BjHDQ70ZxNIg==,type:str] +SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:fHE=,iv:MNRtxZZzKFnAHn8LLOiUKJbHekUcq8vNmDEHYBPYm+A=,tag:w27ZGhzAJa3fX7QQgw2c0Q==,type:str] +SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:RrqmKuvPRi3rSsubaz/9h2LdVoqSI1txmxHicswPgV/E/1mh2VZ3BuuvUGBds6gprbC18PgWjNnaZBGacMI6AvFNRucL01cG3euyGZKJyA==,iv:yN2LwK47xu97jxdYpu9IHrHkbuGesk3OPpBkkaaAtSk=,tag:FvGv0czDWdluoBdmlOLi8w==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:tOPjXmNyn0q+kYtK4F5rg6etqk6hp5+yuc5+N03ObkRFgQ==,iv:WC2Ls3J3XzXdt9Djy0JnYlMNiaWT5Fp1IDXWzGWe8gc=,tag:tqI83ltJkYiCQF5UjCDSbg==,type:str] +SMTP_PASS=ENC[AES256_GCM,data:wXNewdWOxvaZ+s9LId5xzHJ6A5WQpRkTKzk7kBAEUAHAepY2iy1l7W62ePE=,iv:wCgt/HSwae8fuLnjSV7wS6/EWVw5XqNxbs6p9HaXthY=,tag:UHodP4PYlGNNU5RFhDOrsg==,type:str] +SMTP_USER=ENC[AES256_GCM,data:ZuX4HpPffhSZy1x3NrNMPK4CY7c=,iv:2zvisVfHxmZWMIY1S2jKq7mncbU0PbJmIS9QYBCNSdc=,tag:3Fd4HoKLpwNtMehHu1V0oA==,type:str] +ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:SGMj93kLneTedJhzzJLHCnoaSEU=,iv:r/1SdPDEcG5syCG8abaSBckSzlqQ8E+rAwqDNttoqyY=,tag:wa85xP4YWM+ix5Kn3ClJlg==,type:str] +ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:UL//USfKy+IgxEAtnCtx9JhJMVw=,iv:OUPQlDAOoqNMM2ZHdgdmcuP9twotPV+p6VrwaRJ3czk=,tag:gQUd0KEhoVbiMtonu2ceyw==,type:str] +#ENC[AES256_GCM,data:L2euC8ocqeH9DQ1Fgpm5Zv+6jAw=,iv:Y6siwsJ1bxXFubgpx1lpEL4xp6/GrGlSCnbAJKvMcwY=,tag:SoTTKg7MInia/fWY2SG0PQ==,type:comment] +KF_AUTH_URL=ENC[AES256_GCM,data:6sWKG13lAk4QFM8CQAeX/GSra+6tSZy6U2DDRg/utT86yeJVtA==,iv:5W3w81BiQjuDdV8Hu+UsDeQGQ4I2qCzINSgrw0CV88w=,tag:bNkbMnatnizs+dKaSOP2rQ==,type:str] +KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:YJzzZFJlh0EE,iv:mAFUBC0UVhcv+CU6V6D9eREZWFq+xdKynKpIV1pMS6k=,tag:zU7037uBK3K6DEcrKyrSfg==,type:str] +KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:MGFckQuY7PvDok4NC8uavb26yLeoPdw0pnCd/jujY5A3D2PXNME5I6xKwAVLve4jlxqlPgS1Pnuel7pEwG2eMw==,iv:4tHujDdiNKsvh76PRgbPm5i3k5yS5gi6/+xxrjmSs5U=,tag:qY/rCWsXjHZ3KbwIUf34HQ==,type:str] +KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:ICCPMzS8V2CbcXWhv8bTN28IT3YbdCVuhkGB0L/TShFDxQvNuuCepXST8pyTQCWxzuiY44qINdwQiyf4P241zA==,iv:Yy7d3C4uid6x5vqA8cB3i+vjp68AdataVFq3slgKoL8=,tag:ZlmchFCXAXJc6wweoYirUg==,type:str] +APP_URL=ENC[AES256_GCM,data:fJOA0h8+Rgh6bcrWhAW8XqEwPkExyA==,iv:DUBOurc6b8WSbaXxNg7SAW8Bf7g1pfj/gAuepZzH2qc=,tag:JhmteJepQ6J6PzKUYZQFfg==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrTFM5bDlKNWZDUVozb20z\nMi9zUG9EK0JYdFZ6cDd1alVhMGd0a3Fma0NnCjQ1TlJkakpjMDdtUlRJUGJtVUtK\nNUIyMTNVaUgzdElVT0pKWnhwOUVzL0UKLS0tIFFYZ2huK3p2dHFaT2s2bm0vQVh4\nRlN0UVM0aFRzeXAyUkdHWU5URVJaZGcKfXe8NRpAOYM9PZ+uOqxS4dO27GXTc0K6\nH0j4kwyE0yOEay4e4G3AzcKjgrRmRyUs3R0Z9gM75FHH25liYXa1Cw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOTko5Y0N1SnBTd2ZmOHZj\nSFJxbzFRR2NLWFRYV3dqbVpQNkMzMnA2aVNjCmJhdnNXWk1TTCs2NkFqeWhScVJz\nNFJEUTFROTQvN3Azakx6Y1pXWEFPbXcKLS0tIElKMjNHN2tEalZESkJQK3FHMzFH\nT2VONFJVZkFiaTliNHcyOGwvLzY2UlEKzWX6cJJxrv+7v8iacGd57UC69iNPbSek\ns/E9E285dy5wF5gVAsA2wJOoEgENdwRa7EAPcBsMI7VOl0bILGWiQQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpZ0dZYXlESGZJOXB1MlZN\nQnFNaktpSFdKRFlzWDVQZE8vL1ljRnkxR0E0CmdRREtjNEJsQ2xBWmhSMGJKYjB6\naFFoYkZ6RzhLcW9tWFBnQ1VJbEJ6Z1kKLS0tIDJaN05hWFQ5U2xyL1FCZnduVWVQ\nTWlLZ0ZLQ3FqeU5JUWRxT2NXT0hYYm8KaIgXaWz/dGqhCJwIHbwTeoSxs2ansOhr\n/wiwQlXmoM+0EdsMabjOQpdIN6uEtwCwLBnEbKocMPLJ+gPzYvKtIQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2NGlMTXJ1RExKOHVGUVVD\nQURUUm5samc1cnZSR3JjVE41RmdGeDZhV2pjCmtUMHg5bHE5eGxHTytla21IVk9X\nL01HN01vcXl5dkUrM2l4NWpaUUx1NnMKLS0tIDZIQVp4V25DMEVoYzl1NXNXNW1w\nbnhlcTNDVnBkRkF6eUxFcVVLV0JmK2sKyKBrZGPL5Rx/Q+rpzglShyJcjbDiWJEf\nYOnY5JDDB7P08hRW9nB2zcppXAHVfCeUPAWxsN+4BfAQb8FdO8bqwA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKay9XQ1MxOHpOV3VOT3Bt\nOGE3ZCtoL1pBRXNlWDIzNkFpeTJNV0NyVkRrCkgzSk50RC9RUWh5cWxiWVQ4WjZW\na2c2TXMvSmZia29IekRpeGNab3VOWWsKLS0tIDgyZmpXUENFd1orMGVxRUQ1MWoy\nTzd6U0J0MmtMK20wdFZhTFlHOXBpQ0UKdUiBRCW7ZXwTbHEkEoggSQUllJpHJHN5\nOVW4Q4chJ5PZHif7llPuzA9WfSKbLTwigwJMgx5QEkuHoFdLYsMudg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5YjJGMVk1M3ZuYisvT0hR\ndWV0NVl1MGVvQkR3VzV6VVNLVi85aDltT2tVCnI2ZENiWXlDd0xwK1hPR21TL1dr\nWXdoVndlL3liR1F2aG0rNXZPek9qdEUKLS0tIHJMR05IbjlMa2N1RTVkYmhScjhD\nMW5Tbitlc08rNXdWSUZnSWhMQWYzelkK3bPNfxxb7E44O11ehhkIUxqhSEgUoT7o\nrsQSxnZFPuJeXEon1VO8c92SeD+JZy6BzXzYo03gHLRxcVBGput2jQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwVzZWUUVrVVBUOEUraEgx\nTTBmcFYxRWUxU2JacUZJTWZTZUd5aHo1amdZCmhDVVdPeHlETDRSMG5rd1FoTmZX\nbjdQYmRpMW9MWEE1UE8rSENXUjFKM1EKLS0tIHV3bXZBQWExblpENE8relRzb0JF\nYzl1TFZIVnhSZ3dBTktTdGlBeXh6YjgKI5XX2g3fnYUsTlE+nsEMoKF2JDZhNcBS\na6ignKJZ44q0KPPU0gZO2ue1S8XhQkM8hQ9zcWQU+m311qa3lKDPYA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGOG1ueUpIektJazFCT0pj\nVzMvbExOQy9jK1psa25Rd1FjTUZXUmw3YkdjClljdmVHdVNWWHZ4MEpzditOaTNs\nRSt3NFVLOFdEQlhlakduTFpDTjF3aVEKLS0tIFh2bFZKUktqcmx1S0hEK1JvYVRi\nUVJTYkpJUitzT1NOaDhjcFJ2NEl6TzQKLtlw9zz/nSfvxHkNTKWb7ZITpKaNP3L4\nGH+FUZ4cQCJgUoNxtrGFeHBpgz5RCfhkCnVg03B7oZwZO4NZk+YQhg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHL29qZU9SRzZiU2dadnFw\nTk01RzMvSGJjNUVmWjZpWG1PSTN1UExOemtnCnYwR2hUT1JOb0FQRnFGMG5sYVdF\nWGgwNjZzdjAyVkJhU2l3YmRMcHpySncKLS0tIFBTVkdCdkduaG1IYWZyQW9ldG4y\nem9lUHlKRWZsTm9UQUFyWE1NKzVuVG8KCYKl5pmdth3jC50UBQP3CVOljXvRVVCX\nf5UDIP6ytEl9DEG3pe8mSpLPxlNw29fAMzU434b53SUW6q1+y1Yzgw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUbHE4Q0JVdHJIMnNzdFJa\nb1lweEROcyt5UlFzNHEwNndpdkVVdDVjdDBrClFNTVd6WkJCbkpUSFF4ZWVaRENS\nYWhsTURhaXdSMnNkU2REZDBONUZDejgKLS0tIHFRN1dXQzlYTzd3TTFtVTJGcDd1\nU1hqUkN2dFFSNVl0d1F3NDNtS3B3VlkKpN8Gn9oTEjCozAiYXT0ZUdXAc2wAKaED\n8oJ5r7aRuEsjidoBVNK6fgb1Jgk8qpa0g1/A0SSQRLXHspDrEz2HYg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQRTRKdGkyR3AvRDYrdzlj\nMDBxMUpSRHk3OFplTDMwbXlPTXdlWnlJM2o0CkxLUXRPUDFRWE1HR0FUb3VIOURw\nNlFhS1Z5TWdUUzhKSm45RXJUNmJrRGsKLS0tIFFjbkdLTDZBaG9jRS9qVzByeW4z\nYTJDdTMxYVo4OUt6UnBUMDdyZUtCM1EK9RVBqUv5IhF+zvkYbS+Sv/ylYiQ/2Fyj\n+DP/hJtziGHCSo7rhosNwl2Cg+QXJnNMlw4EEKodlOcyEDHZUd/MuA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_lastmodified=2026-05-17T02:33:30Z -sops_mac=ENC[AES256_GCM,data:mz8tWY+WNLhcwTxQo+NvxyDLKHLVW8T0SrGrxRUnqzVuF/RtmEF/cXt/tNuB91tt8nCXNRHxGmvzNglUOytCVGZwZ9pB7rMMY38EcEYGn2weQS6zhOXFg+dX31HsgYa5/uMqPrOlmGEb1IHSj0lRtRMZ6HNakEaQGd5rGzQdzGQ=,iv:cn0cIvKtW16gKIy8p72soVuKJdsjZvSEnE0yR0umxNM=,tag:UdbXCfyxx4p7CmMkxNLgSQ==,type:str] +sops_lastmodified=2026-05-17T03:23:35Z +sops_mac=ENC[AES256_GCM,data:aVDfMyoI4MZSPrGsYxPoFPuoAZRzrKo+aISv9EMPFjSb2dCp2sZbNi6beeMnd2UjLLHiivbFHRH9FgB4arzppTALJ+zTxA7oCzVejAte9up+o52/DQQ1eQyywaB1geyjxr58EBuHAEthz5FMm+X6Cf9Oa6iE6x84aRZ9Per1kfw=,iv:sK6WhmyvZmrmzwKrdwsTaDMqK5yTIChP8V46ra7OfMA=,tag:bxEM8WoaZSegeNSxKjVaBQ==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 From 17046d2baf1a3a852a70f9d771fcb72b869bdfd2 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sun, 17 May 2026 00:29:37 -0400 Subject: [PATCH 07/16] Handle redirect for subdomain and custom --- server/kf/api.ts | 231 +++++++++++++++++++++++++++------------------- server/kf/auth.ts | 47 +++++++++- 2 files changed, 179 insertions(+), 99 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index 4972b4e11..a01b60980 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -2,9 +2,10 @@ * KF Auth integration routes for PubPub. * * OIDC login/callback: - * GET /auth/login — redirect to KF Auth - * GET /auth/callback — handle OIDC callback, create session - * POST /auth/logout — clear session + redirect to KF Auth logout + * GET /auth/login — redirect to KF Auth + * GET /auth/callback — handle OIDC callback, create session + * GET /auth/session-set — establish session on custom domains (via encrypted token) + * POST /auth/logout — clear session + redirect to KF Auth logout * * Internal service-to-service endpoints (KF_INTERNAL_API_KEY): * POST /api/kf/profile-sync — receive profile updates from KF Auth @@ -17,6 +18,7 @@ * POST /api/kf/transfer-community — transfer community ownership to a different KF Account */ +import { Op } from 'sequelize'; import { timingSafeEqual } from 'crypto'; import { Router } from 'express'; import { promisify } from 'util'; @@ -25,9 +27,19 @@ import { Collection, Community, Member, Pub, PubAttribution, Release, User } fro import { sequelize } from 'server/sequelize'; import { getHashedUserId } from 'utils/caching/getHashedUserId'; import { ensureUserIsCommunityAdmin } from 'utils/ensureUserIsCommunityAdmin'; -import { isDuqDuq, isProd } from 'utils/environment'; - -import { buildAuthorizeUrl, exchangeCode, fetchUserInfo, fetchUserOrgs, KF_AUTH_URL } from './auth'; +import { isDevelopment, isDuqDuq, isProd } from 'utils/environment'; +import { slugifyString } from 'utils/strings'; + +import { + buildAuthorizeUrl, + decryptPayload, + encryptPayload, + exchangeCode, + fetchUserInfo, + fetchUserOrgs, + generateCodeVerifier, + KF_AUTH_URL, +} from './auth'; // ── Helpers ────────────────────────────────────────────────────────── @@ -55,15 +67,24 @@ function requireInternalKey(req: any, res: any, next: () => void): void { /** * Derive the community hostname the user came from. * Needed because the OIDC callback always hits the main domain. + * Note: PubPub's hostname middleware rewrites duqduq.org → pubpub.org + * for community resolution, so we reverse that here. */ function getCommunityHost(req: any): string { - // Use the communityHostname header if set by the reverse proxy, - // otherwise fall back to the raw hostname. - return req.headers.communityhostname || req.hostname; + const host: string = req.headers.communityhostname || req.hostname; + if (isDuqDuq() && host.includes('pubpub.org')) { + return host.replace('pubpub.org', 'duqduq.org'); + } + return host; } -// Cookie name for OIDC state (verifier stored in session for custom domain compat) -const STATE_COOKIE = 'kf_oauth_state'; +/** + * Returns true if the host is a platform subdomain (*.pubpub.org or *.duqduq.org) + * where the shared session cookie works across subdomains. + */ +function isPlatformSubdomain(host: string): boolean { + return host.endsWith('.pubpub.org') || host.endsWith('.duqduq.org'); +} // ── Router ─────────────────────────────────────────────────────────── @@ -80,35 +101,15 @@ router.get('/auth/login', (req: any, res: any) => { ? rawReturn : '/'; - // Encode the community hostname + return path in state so we can - // redirect back after the OIDC callback. - const statePayload = JSON.stringify({ host: communityHost, returnTo }); - const stateToken = Buffer.from(statePayload).toString('base64url'); - - const { url, codeVerifier } = buildAuthorizeUrl(stateToken); - - const cookieOpts = { - httpOnly: true, - secure: isProd(), - sameSite: 'lax' as const, - path: '/', - maxAge: 600_000, // 10 minutes - // Set on .pubpub.org so the callback (on www.pubpub.org) can read it - ...(isProd() && - communityHost.indexOf('pubpub.org') > -1 && { - domain: '.pubpub.org', - }), - }; + // Generate verifier first, then encrypt it with routing info into state. + // This avoids cookies/session for OIDC state, so it works across + // domains (custom domains → callback on www.duqduq.org). + const codeVerifier = generateCodeVerifier(); + const stateToken = encryptPayload({ v: codeVerifier, h: communityHost, r: returnTo }); - res.cookie(STATE_COOKIE, stateToken, cookieOpts); + const { url } = buildAuthorizeUrl(stateToken, codeVerifier); - // Store verifier in session (not cookie) so it works across domains. - // Custom domain sessions are scoped to their domain, and the callback - // hits the same domain since PubPub proxies all requests. - req.session.kfOauthVerifier = codeVerifier; - req.session.save(() => { - return res.redirect(url); - }); + return res.redirect(url); }); // ─── OIDC callback ─────────────────────────────────────────────────── @@ -119,30 +120,24 @@ router.get('/auth/callback', async (req: any, res: any) => { if (error) { console.error('KF Auth error:', error, req.query.error_description); - return res.redirect('/login?error=auth_failed'); + return res.status(400).send('Authentication failed. Please try again.'); } if (!code || !state) { - return res.redirect('/login?error=missing_params'); + return res.status(400).send('Missing authentication parameters.'); } - // Validate state - const savedState = req.cookies[STATE_COOKIE]; - const codeVerifier = req.session?.kfOauthVerifier; - - // Clear OIDC state - res.clearCookie(STATE_COOKIE, { path: '/' }); - if (req.session) { - delete req.session.kfOauthVerifier; - } - - if (!savedState || savedState !== state) { - return res.redirect('/login?error=invalid_state'); + // Decrypt state → {v: codeVerifier, h: host, r: returnTo} + const stateData = decryptPayload<{ v: string; h: string; r: string }>(state); + if (!stateData || !stateData.v) { + return res.status(400).send('Invalid or expired authentication state.'); } - if (!codeVerifier) { - return res.redirect('/login?error=missing_verifier'); - } + const { v: codeVerifier, h: host, r: rawReturn } = stateData; + const returnTo = + typeof rawReturn === 'string' && rawReturn.startsWith('/') && !rawReturn.startsWith('//') + ? rawReturn + : '/'; // Exchange authorization code for tokens const tokens = await exchangeCode(code, codeVerifier); @@ -151,62 +146,106 @@ router.get('/auth/callback', async (req: any, res: any) => { const userInfo = await fetchUserInfo(tokens.access_token); const kfUserId = userInfo.sub; - // Look up PubPub user by ID (PubPub ID = KF Auth ID after seeding) - const user = await User.findOne({ where: { id: kfUserId } }); + // Look up PubPub user by ID, or auto-create on first login + let user = await User.findOne({ where: { id: kfUserId } }); if (!user) { - console.error(`No PubPub user found for KF Auth ID: ${kfUserId}`); - return res.redirect('/login?error=user_not_found'); + const firstName = (userInfo.given_name || userInfo.name || 'New').trim(); + const lastName = (userInfo.family_name || 'User').trim(); + const fullName = `${firstName} ${lastName}`; + const initials = `${firstName[0] || '?'}${lastName[0] || '?'}`; + const baseSlug = slugifyString(fullName) || 'user'; + const existingSlugCount = await User.count({ + where: { slug: { [Op.like]: `${baseSlug}%` } }, + }); + const slug = existingSlugCount ? `${baseSlug}-${existingSlugCount + 1}` : baseSlug; + + user = await User.create({ + id: kfUserId, + slug, + firstName, + lastName, + fullName, + initials, + email: userInfo.email || `${kfUserId}@placeholder.invalid`, + avatar: userInfo.picture || null, + } as any); + console.log(`Auto-created PubPub user ${user.id} (${user.slug}) from KF Auth`); } - // Create a standard Passport session (indistinguishable from old login) + const protocol = isDevelopment() ? 'http' : 'https'; + + // For custom domains, we can't set a session here (different domain). + // Create a one-time encrypted token and redirect to session-set on the origin. + if (host && !isPlatformSubdomain(host)) { + const sessionToken = encryptPayload({ + u: user.id, + r: returnTo, + exp: Date.now() + 60_000, // 60 seconds + }); + const sessionSetUrl = `${protocol}://${host}/auth/session-set?token=${encodeURIComponent(sessionToken)}`; + return res.redirect(sessionSetUrl); + } + + // For platform subdomains: create session directly (shared cookie on .pubpub.org / .duqduq.org) const logIn = promisify(req.logIn.bind(req)); await logIn(user); - // Set the CDN cache cookie const hashedUserId = getHashedUserId(user); res.cookie('pp-lic', `pp-li-${hashedUserId}`, { - ...(isProd() && - req.hostname.indexOf('pubpub.org') > -1 && { - domain: '.pubpub.org', - }), - ...(isDuqDuq() && - req.hostname.indexOf('duqduq.org') > -1 && { - domain: '.duqduq.org', - }), + ...(isProd() && { domain: '.pubpub.org' }), + ...(isDuqDuq() && { domain: '.duqduq.org' }), maxAge: 30 * 24 * 60 * 60 * 1000, }); - // Parse state to get the community host + return path - let redirectUrl = '/'; - try { - const statePayload = JSON.parse(Buffer.from(state, 'base64url').toString()); - const host = statePayload.host || ''; - const rawReturn = statePayload.returnTo || '/'; - // Validate returnTo is a safe relative path - const returnTo = - typeof rawReturn === 'string' && - rawReturn.startsWith('/') && - !rawReturn.startsWith('//') - ? rawReturn - : '/'; - - if (host && host !== req.hostname) { - // Redirect back to the community the user came from - const protocol = isProd() ? 'https' : 'http'; - redirectUrl = `${protocol}://${host}${returnTo}`; - } else { - redirectUrl = returnTo; - } - } catch { - // If state parsing fails, just go to root - redirectUrl = '/'; + if (host && host !== req.hostname) { + return res.redirect(`${protocol}://${host}${returnTo}`); } - - return res.redirect(redirectUrl); + return res.redirect(returnTo); } catch (err) { console.error('OIDC callback error:', err); - return res.redirect('/login?error=callback_failed'); + return res.status(500).send('Login failed. Please try again.'); + } +}); + +// ─── Session transfer for custom domains ───────────────────────────── + +router.get('/auth/session-set', async (req: any, res: any) => { + try { + const { token } = req.query; + if (!token) { + return res.status(400).send('Missing session token.'); + } + + const data = decryptPayload<{ u: string; r: string; exp: number }>(token); + if (!data || !data.u) { + return res.status(400).send('Invalid session token.'); + } + + if (Date.now() > data.exp) { + return res.status(400).send('Session token expired. Please log in again.'); + } + + const user = await User.findOne({ where: { id: data.u } }); + if (!user) { + return res.status(400).send('User not found.'); + } + + // Create Passport session on this domain + const logIn = promisify(req.logIn.bind(req)); + await logIn(user); + + // Set the CDN cache cookie on this domain + const hashedUserId = getHashedUserId(user); + res.cookie('pp-lic', `pp-li-${hashedUserId}`, { + maxAge: 30 * 24 * 60 * 60 * 1000, + }); + + const returnTo = data.r || '/'; + return res.redirect(returnTo); + } catch (err) { + console.error('Session-set error:', err); + return res.status(500).send('Failed to establish session. Please try again.'); } }); diff --git a/server/kf/auth.ts b/server/kf/auth.ts index 850bc028d..79a7ab4c1 100644 --- a/server/kf/auth.ts +++ b/server/kf/auth.ts @@ -13,6 +13,43 @@ const KF_AUTH_CLIENT_SECRET = process.env.KF_AUTH_CLIENT_SECRET ?? ''; const APP_URL = process.env.APP_URL ?? 'http://localhost:9876'; const REDIRECT_URI = `${APP_URL}/auth/callback`; +// ── Symmetric encryption (AES-256-GCM) ────────────────────────────── + +/** Derive a 32-byte key from the client secret for AES-256-GCM. */ +function deriveKey(): Buffer { + return crypto.createHash('sha256').update(KF_AUTH_CLIENT_SECRET).digest(); +} + +/** Encrypt a JSON-serializable object → base64url token. */ +export function encryptPayload(data: object): string { + const key = deriveKey(); + const iv = crypto.randomBytes(12); + const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); + const plaintext = JSON.stringify(data); + const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); + const tag = cipher.getAuthTag(); + // Layout: iv (12) + tag (16) + ciphertext + return Buffer.concat([iv, tag, encrypted]).toString('base64url'); +} + +/** Decrypt a base64url token → parsed object, or null on failure. */ +export function decryptPayload(token: string): T | null { + try { + const key = deriveKey(); + const buf = Buffer.from(token, 'base64url'); + if (buf.length < 29) return null; // iv(12) + tag(16) + at least 1 byte + const iv = buf.subarray(0, 12); + const tag = buf.subarray(12, 28); + const ciphertext = buf.subarray(28); + const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); + decipher.setAuthTag(tag); + const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + return JSON.parse(decrypted.toString('utf8')) as T; + } catch { + return null; + } +} + // BetterAuth OIDC endpoints const AUTHORIZE_PATH = '/api/auth/oauth2/authorize'; const TOKEN_PATH = '/api/auth/oauth2/token'; @@ -32,13 +69,17 @@ export function generateCodeChallenge(verifier: string): string { /** * Build the URL to redirect the user to for authentication. - * `state` should include the community subdomain/domain for post-login redirect. + * `state` is the OIDC state parameter (encrypted payload with verifier + routing info). + * `existingVerifier` allows passing a pre-generated verifier (when it's encrypted in state). */ -export function buildAuthorizeUrl(state: string): { +export function buildAuthorizeUrl( + state: string, + existingVerifier?: string, +): { url: string; codeVerifier: string; } { - const codeVerifier = generateCodeVerifier(); + const codeVerifier = existingVerifier ?? generateCodeVerifier(); const codeChallenge = generateCodeChallenge(codeVerifier); const params = new URLSearchParams({ client_id: KF_AUTH_CLIENT_ID, From b5bc32fbe29571f4e7d973ae365afc3d5f98c23f Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sun, 17 May 2026 00:29:48 -0400 Subject: [PATCH 08/16] lint --- server/kf/api.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index a01b60980..fad848253 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -18,9 +18,9 @@ * POST /api/kf/transfer-community — transfer community ownership to a different KF Account */ -import { Op } from 'sequelize'; import { timingSafeEqual } from 'crypto'; import { Router } from 'express'; +import { Op } from 'sequelize'; import { promisify } from 'util'; import { Collection, Community, Member, Pub, PubAttribution, Release, User } from 'server/models'; @@ -135,7 +135,9 @@ router.get('/auth/callback', async (req: any, res: any) => { const { v: codeVerifier, h: host, r: rawReturn } = stateData; const returnTo = - typeof rawReturn === 'string' && rawReturn.startsWith('/') && !rawReturn.startsWith('//') + typeof rawReturn === 'string' && + rawReturn.startsWith('/') && + !rawReturn.startsWith('//') ? rawReturn : '/'; From 2eab36eebedb7a6f7955cccd7c1a059cdef07411 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sun, 17 May 2026 01:06:51 -0400 Subject: [PATCH 09/16] fix new accoutn on the fly --- server/kf/api.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index fad848253..6d66d6ee2 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -2,7 +2,7 @@ * KF Auth integration routes for PubPub. * * OIDC login/callback: - * GET /auth/login — redirect to KF Auth +docker service logs auth_auth --tail 50 2>&1 | grep -i "error\|invalid\|authorize\|token" * GET /auth/login — redirect to KF Auth * GET /auth/callback — handle OIDC callback, create session * GET /auth/session-set — establish session on custom domains (via encrypted token) * POST /auth/logout — clear session + redirect to KF Auth logout @@ -160,7 +160,20 @@ router.get('/auth/callback', async (req: any, res: any) => { const existingSlugCount = await User.count({ where: { slug: { [Op.like]: `${baseSlug}%` } }, }); - const slug = existingSlugCount ? `${baseSlug}-${existingSlugCount + 1}` : baseSlug; + const slug = existingSlugCount + ? `${baseSlug}-${existingSlugCount + 1}` + : baseSlug; + + // Use KF Auth email if available and not already taken + let email = `${kfUserId}@placeholder.invalid`; + if (userInfo.email) { + const emailTaken = await User.findOne({ + where: { email: userInfo.email.toLowerCase() }, + }); + if (!emailTaken) { + email = userInfo.email.toLowerCase(); + } + } user = await User.create({ id: kfUserId, @@ -169,8 +182,10 @@ router.get('/auth/callback', async (req: any, res: any) => { lastName, fullName, initials, - email: userInfo.email || `${kfUserId}@placeholder.invalid`, + email, avatar: userInfo.picture || null, + hash: '', + salt: '', } as any); console.log(`Auto-created PubPub user ${user.id} (${user.slug}) from KF Auth`); } @@ -204,9 +219,10 @@ router.get('/auth/callback', async (req: any, res: any) => { return res.redirect(`${protocol}://${host}${returnTo}`); } return res.redirect(returnTo); - } catch (err) { + } catch (err: any) { console.error('OIDC callback error:', err); - return res.status(500).send('Login failed. Please try again.'); + const detail = isDuqDuq() ? ` (${err?.message || err})` : ''; + return res.status(500).send(`Login failed. Please try again.${detail}`); } }); From 398ddd1a7238d18f117e0a7861f250c1d203b262 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sun, 17 May 2026 01:07:11 -0400 Subject: [PATCH 10/16] lint --- server/kf/api.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index 6d66d6ee2..8dec6de77 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -160,9 +160,7 @@ router.get('/auth/callback', async (req: any, res: any) => { const existingSlugCount = await User.count({ where: { slug: { [Op.like]: `${baseSlug}%` } }, }); - const slug = existingSlugCount - ? `${baseSlug}-${existingSlugCount + 1}` - : baseSlug; + const slug = existingSlugCount ? `${baseSlug}-${existingSlugCount + 1}` : baseSlug; // Use KF Auth email if available and not already taken let email = `${kfUserId}@placeholder.invalid`; From 6275f2bd6719c2b7178f355d79cf297b88c33cc6 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sun, 17 May 2026 15:53:55 -0400 Subject: [PATCH 11/16] Add context endpoints --- server/kf/api.ts | 36 ++++++++++++++++++++++++++++++------ server/kf/auth.ts | 2 ++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index 8dec6de77..4fa4161cf 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -107,7 +107,7 @@ router.get('/auth/login', (req: any, res: any) => { const codeVerifier = generateCodeVerifier(); const stateToken = encryptPayload({ v: codeVerifier, h: communityHost, r: returnTo }); - const { url } = buildAuthorizeUrl(stateToken, codeVerifier); + const { url } = buildAuthorizeUrl(stateToken, codeVerifier, communityHost); return res.redirect(url); }); @@ -329,6 +329,29 @@ router.post('/api/kf/profile-sync', requireInternalKey, async (req: any, res: an } }); +// ─── Context listing (for KF Auth playground) ─────────────────────── + +router.get('/api/kf/contexts', requireInternalKey, async (req: any, res: any) => { + try { + const communities = await Community.findAll({ + attributes: ['subdomain', 'title', 'avatar'], + order: [['title', 'ASC']], + limit: 200, + }); + + return res.json( + communities.map((c: any) => ({ + slug: c.subdomain, + title: c.title, + avatar: c.avatar || null, + })), + ); + } catch (err) { + console.error('Contexts listing error:', err); + return res.status(500).json({ error: 'Internal error' }); + } +}); + // ─── Branding API (for KF Auth login page) ─────────────────────────── router.get('/api/kf/branding', requireInternalKey, async (req: any, res: any) => { @@ -357,11 +380,12 @@ router.get('/api/kf/branding', requireInternalKey, async (req: any, res: any) => } return res.json({ - communityName: community.title, - logoUrl: community.avatar || community.headerLogo, - accentColorLight: community.accentColorLight, - accentColorDark: community.accentColorDark, - headerLogo: community.headerLogo, + // Fields expected by kf-auth's loadAppContext() + display_name: community.title, + logo_url: community.avatar || community.headerLogo || null, + brand_color: community.accentColorDark || null, + background_color: community.accentColorLight || null, + // Extra fields for backwards compat / debugging subdomain: community.subdomain, }); } catch (err) { diff --git a/server/kf/auth.ts b/server/kf/auth.ts index 79a7ab4c1..be1730417 100644 --- a/server/kf/auth.ts +++ b/server/kf/auth.ts @@ -75,6 +75,7 @@ export function generateCodeChallenge(verifier: string): string { export function buildAuthorizeUrl( state: string, existingVerifier?: string, + context?: string, ): { url: string; codeVerifier: string; @@ -89,6 +90,7 @@ export function buildAuthorizeUrl( state, code_challenge: codeChallenge, code_challenge_method: 'S256', + ...(context && { context }), }); return { url: `${KF_AUTH_URL}${AUTHORIZE_PATH}?${params}`, codeVerifier }; } From 5aa45b05b7dabfffc6ce7fd182c308d8ca47e717 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sun, 17 May 2026 16:42:05 -0400 Subject: [PATCH 12/16] context fix for login --- server/kf/api.ts | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index 4fa4161cf..2359288df 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -107,6 +107,8 @@ router.get('/auth/login', (req: any, res: any) => { const codeVerifier = generateCodeVerifier(); const stateToken = encryptPayload({ v: codeVerifier, h: communityHost, r: returnTo }); + // Pass the community hostname as context for per-community branding. + // The branding endpoint resolves hostnames → slugs. const { url } = buildAuthorizeUrl(stateToken, codeVerifier, communityHost); return res.redirect(url); @@ -357,14 +359,22 @@ router.get('/api/kf/contexts', requireInternalKey, async (req: any, res: any) => router.get('/api/kf/branding', requireInternalKey, async (req: any, res: any) => { try { const { subdomain, context } = req.query; - const slug = context || subdomain; + let identifier = context || subdomain; - if (!slug) { + if (!identifier) { return res.status(400).json({ error: 'subdomain or context param required' }); } - const community = await Community.findOne({ - where: { subdomain: slug }, + // If the identifier looks like a platform hostname, extract the subdomain slug. + // e.g. "mycommunity.duqduq.org" → "mycommunity", "mycommunity.pubpub.org" → "mycommunity" + const platformMatch = identifier.match(/^([^.]+)\.(pubpub\.org|duqduq\.org)$/); + if (platformMatch && platformMatch[1] !== 'www') { + identifier = platformMatch[1]; + } + + // Try by subdomain slug first + let community = await Community.findOne({ + where: { subdomain: identifier }, attributes: [ 'title', 'avatar', @@ -375,6 +385,21 @@ router.get('/api/kf/branding', requireInternalKey, async (req: any, res: any) => ], }); + // If not found and identifier looks like a hostname, try as custom domain + if (!community && identifier.includes('.')) { + community = await Community.findOne({ + where: { domain: identifier }, + attributes: [ + 'title', + 'avatar', + 'headerLogo', + 'accentColorLight', + 'accentColorDark', + 'subdomain', + ], + }); + } + if (!community) { return res.status(404).json({ error: 'Community not found' }); } From cf8c58e064caf9cb61b5c7128f23882854e1ee83 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Sun, 17 May 2026 23:04:14 -0400 Subject: [PATCH 13/16] Fix local auth dev --- .gitignore | 2 + infra/.env.local.enc | 103 ++++++++++++++++++++++--------------------- server/kf/auth.ts | 13 +++--- 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index 0069a25b7..717ce9e8c 100755 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,5 @@ tsconfig.tsbuildinfo infra/pgdata/ tmp/ + +planning/ \ No newline at end of file diff --git a/infra/.env.local.enc b/infra/.env.local.enc index 3b8465871..61d94c74a 100644 --- a/infra/.env.local.enc +++ b/infra/.env.local.enc @@ -1,59 +1,60 @@ -AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:XRrbCOuZdK8OysMC8g+jAaifrbpYSxVywFEbb3HjnUfoHizjXn+NlWOUAVmpqN5BGo8diM34p2EVduU1XZyzSQ==,iv:jqCPzyTAdqSGOiCFMT88AkvZFYVeEfcwRfu7IwpBlsA=,tag:Dm7+P6d5FXCH0iC016L0Tg==,type:str] -ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:XuAZOjWIOkJ07lO4XrAvlUvn693sA32GWSF4OgdkG3IJqr8UBEtSnuIcEIbdcF9/ANLBoWFgW3PXrZ70jdGDYw==,iv:3MfHLwJisbdD1FuNCN53y04pGsrZdHwFKvWZ4TiUCXw=,tag:TpZf6DbGqzTKqeaXVmmO5w==,type:str] -AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:n5D8UKoeZwKWuwMXuSlxOm1TRxw=,iv:R4d7oQkeBYixr2QzugIvPvbuy8fViR2FcNiPw7ielhQ=,tag:HwWiw9fDS6o9Jn2nKYs79A==,type:str] -AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:b1YKnZq9gAs6unTXjtfsOYOq3Rc=,iv:OFm9qoskNddUO+Yh5V+tY6lDbhvF74OvRFRR8gvw2Vs=,tag:F37DWCeP/BbV/mL5Jt8xiQ==,type:str] -AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:Q7HVjPiubI93ZRPzVMmAUfqnB5wQmDlyMiL3ZuswImBmSPigHafEcQ==,iv:VBi7efe5hJ9cqfyS48w/QZNekyOA1Gd/Kb2I7r3MLro=,tag:9LNAKj/DWpK8B31abTjq9w==,type:str] -AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:dDjdPGyr483URjl+YkPmxtYsoGxhkTjFMBvdJShK43WdYgvWaFQNJw==,iv:myfObHUHH0m7nWYzrr8RrphaBffuqOuM2qdTYUASI2E=,tag:+bsEDlphkGMijOdVvMhzjA==,type:str] -BACKUPS_SECRET=ENC[AES256_GCM,data:oO4V9V5YSE1bGYv/Kr7Vxb3Xw/5qR+SveidcUmbdnBzuZhaCtde/MUZbmNM=,iv:bz7Pxf4P0XX7pDFT2axgjiY7ONner71ZWiyqpDzwk5o=,tag:be2p6MhRvvO1rYoPx3Q3fQ==,type:str] -CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:rzOukQvL+WIJ35sOLwlpX+xxUp3fmhVOhPzmQrqgVKI/Awrzp3bFdt0pPTcpnxQ2IirUCvs=,iv:RQF3iClC4OivxbvCP1jVmsduPcB7IRAsQS+VZ537M10=,tag:10AxZDcl8L94X+Ku76CgrQ==,type:str] -CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:iFvN6NpQH/KJ2mjPPcL38kM33Z7/eqbI6+uebsYuLVEs7F3yh/FOuoueb9TaTHn3DHUkzQ8=,iv:BHlee1tudmEAk3gfUkjNFftRBnAyeB8wH/pwcXxgVBA=,tag:H57LDqLFp9RH8G8vQ0NwCw==,type:str] -CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:9NbqU9+RF1BTNYBJKYoiFpckgEmQIj2RXkVNwOxa9x0=,iv:qGRIa+opCaodDmwwj8z3n3ppu8RWbzrnQZ2tG5d5s60=,tag:t1YW0H6vr+1+ObdHJ0hEDA==,type:str] -CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:MZxp1vXHUtQbnSRC/1T7tRAVYTu8I2sIEwzkn/9srDA=,tag:skSW6FWPkMGvJrZDE8434A==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:7G44ivPxFMcsHTnssCzLCJVS9IZt5CnFpxIHIZahMEol2N/As1mN6qeb3ds=,iv:ixUkDZPnSchGhXQN/feorKtgSMdnmpBy9XeatJE3lPE=,tag:LBu5Rw+nMedECjwpP3rqag==,type:str] -DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:S3j/ilWNZs64uWEsV4qEvft07mXoTbs7ktheR20RiZwzcw==,iv:avmtNQWnEr/1svDYhVHxcdUg5IlGlm1znuYF7k4EglM=,tag:QBcmlkfdzHayGMfCS0377w==,type:str] -DOI_LOGIN_ID=ENC[AES256_GCM,data:tj3fwNl0,iv:Ndpq/BV/V1n5sdRdnOIpd5q8KdaGxESLI1gZDAr3huQ=,tag:FT+G0TYVYPdouH8L0jawEg==,type:str] -DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:6oVZvxbpyGL/uTE8Fs5TItys5ng=,iv:OiPNIe2OwBb+hsrihtrzAOZRq1T1q8TnOebgH/JXqsM=,tag:uYsY8P+/RA5hKwoayTLQUg==,type:str] -DOI_SUBMISSION_URL=ENC[AES256_GCM,data:CSrSedSfIY/JjhC7EPGIrDZsjJ+3hnPh+2jZuR3QLYkpIE1Otqu55A4=,iv:SLpluAPerRk1fEypwZMSQqEAiVJtjoiA0esgFQ2+mcs=,tag:HOt7XzTHNe9WGx832UKibQ==,type:str] -FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:q3yhtanJhbVPpkyTbUUT5XV4WJqvCCKbkIILsgecnG4=,iv:vjMlrhllN0CwVepu4nb5nTVNgL8NR6w/wY7Xu0rLB1A=,tag:NofwqwvS8y90R0sq5tiI5Q==,type:str] -FASTLY_SERVICE_ID=ENC[AES256_GCM,data:niSYhU/1i0ydC0hOglDNMx2wuWsCCQ==,iv:tjat4pUfC8/go2YQq+ZMUrbCopnfgEZ6USRY3fotoU4=,tag:Ivfr6TaLGiQ2/OSdUl8hVg==,type:str] -FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:0/5MlEPOUE2BZzhpqUqBXVsBy9jVRtzrAQqQZftSep4=,tag:sZeILTq2Y1DsvI4axQKNtw==,type:str] -IS_DUQDUQ=ENC[AES256_GCM,data:1qc8rQ==,iv:+c7K3tT3+TkXpQgsxwsUdrduoe0vbmkYrhzka94ydgo=,tag:uPUCSDFj5Upz9DncVa4I0A==,type:str] -JWT_SIGNING_SECRET=ENC[AES256_GCM,data:Wk5Mo7pnugtsZTiN1+RrWlVHfYrYuRLhnxLZaKfoN3dXztCuFv0BVX727APcHi8G5/8l2aydXZJRq7czlXzv+t5Ss81PqT3AidHR8/jLRuy4nonAO+mhMapgafO8cev0ezfOIV/ng1PQS13JRBObn6aM0oLeTZKFtGBpKYP8FyRLjAb2HlpIysQel4SAOR/1aUtNx1lPylunNVs8TjmKFFk0b4gMwdI1BBXldKn1bV/y8BkmEWk3ZkKYh9bloE71lXj+VSdwzeFk+cbJYAfPl4yUV+Ss/T+vdjjst5XrLQehZBXHJ9i8rInP5WcBfzMqXjxm61q0kPAyALQ7yLk1+Pi/OL+EpVAQkbyVJraMO+49EQsNGh8DFTKfAdW9Ef1PgbJbv6oyU3b3940/xaJeQgbDxJ+XICJTtJKRaxC+DGVTg2EgnxU+HzmWKKjHnxGLt5EP9KxKhp8NqSh6vBnofbrFtEybG8EsAPiCu0XHHhIWrUhx8ShWAAnoAVwQT6pkp06/n6gssK1Imoqi5YfJcFdoLWCgZA+8Sgu3rdrqqBu6744fgSp2HAxJ6pfT197Hd8mIyRegVtyibPxjigPEIb1kDN2b0cwLknXH9ywErNCqL63RBpxbg7EfuHQhCKTgwcEuze9fOGxWPFNWWX9ilYasj7D96q+NwgX2iuFs3unuGpx0Qekl6fMegkuKw8MKeG29GMvWqbRGsq35VWTPR2cMJZ5VfJINCDu5NbI5FSGS0doSIRzVs/fYfUZ+TKgeZbVM9dY2PETFmVwXRPEgxeIxlP1okjsPP+bZnRmzNED5yOunOo6o9cK7E3G+gzsuVKQW8zLxP80HbNcI4fAflOAyligJpBh15A1hKNdhm63n41h6kiGecX+gorjb+q141oIq+CCsU+AcGs5yGJ1+VoWbA7FFLd8n8RNaa3NXZyiL0qsjGg6+EzfFwdKj23ChLK4r+Tqw6LPLYtsHF/KkVQbLGxwhNpDs0aZT4hJbiCpjV6N9UyuBDl9HEG/loYvHfswBLbnKoagB76qFCWuATUAeJEixFAA7niuRdEaEhiNapFOgKfHFpZ3ZcfGQn2++IsemEeEnECeS/aRTM/3p9SNNOv6c9Z7DUCVLAELrRqi/mwEg7BNh3amyt4cE7NiW6Mh1zzrFW9jl4Ny8G2AZiydjkTlmNcXJMSrU5z/bxWqdWQrKCtBTYkLCxnv/xLRHxF4F+m0ZOcJvESXnyWgzDFyNkrPOKNOQGO8rhDRQpu1ET4+SqbIvyhIiloT9U2ugGhzSJB5YW8wn9/f0rb9cFcx7r4kbhFerno0FPHUoyoQ+8RCoXcvNkdaL/3uiJYE+jiBiXDUvnCEbA5RYYHjafg==,iv:PP4PbASMABdy8mQeNpyWI9FeMjWhRNJ8nycgiaLXBKA=,tag:igUoRywjNWwreBkgdEW36w==,type:str] -MAILCHIMP_API_KEY=ENC[AES256_GCM,data:CIWa32ygnpkK6QPoq0FVw9YWaXZrd2LFbjOYDTrt2wOACfmr,iv:ooTxYS/0p3/6TMde7+/Y7w+h83CEAzcRDTIXjz5lZ/Q=,tag:s9pG1ttTwSiVtLXnatdjmw==,type:str] -MAILGUN_API_KEY=ENC[AES256_GCM,data:nPH6Km5hixRtj+btl7WHAUYGkONxHmcDbf1H/Bpt0TWgC47K,iv:aHpsZo2EAzPzjfg1/mp11ucH3qkQaRQfySM17PlxV6g=,tag:3cuYn5jLt3F13Scq+nmZNA==,type:str] -NODE_ENV=ENC[AES256_GCM,data:1isuh3tOeAJGcw==,iv:AfPbLyMVmBArq26Fs01ZmMc43HMtws2EnQXlOwsmESM=,tag:DVyzGVtVutoSPGL4ZeIBFA==,type:str] -S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:IINWnDfgucDNxdFzpZRcOduZaeQ=,iv:aIadDQwcnXocicLhMf2pEeUQJu9iqgprkL6wGYFe+Mk=,tag:6Yyg5aHJcYR4yk7I/0jALQ==,type:str] -S3_BACKUP_BUCKET=ENC[AES256_GCM,data:aq9F254VFxzc0Cw=,iv:0wltZ9vfiP8tEgf06FGxQP4y8KdZ7U3rS8csQiY4y5Y=,tag:aYs7N/azr2+boByKnEJ6iQ==,type:str] -S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:DdUQumcLYE7SZegJXxUcsdOKz37KGenLXy2fyzvbDj5DG/E=,iv:JUsYadOwVEqpGsIa6mEzvLQ72ISC1F59HnTxbW5mJIg=,tag:Xj2HVARtYEPRT+BB64F27A==,type:str] -S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:4/naQ8RAJSEU2caBe1P4nHTL7uG202WyXg6M15ZfiUXGs0d4dwzWnw==,iv:7N/t00CkNVMdMS6RhYFICxRu/jW0DtCGvvGYNPmBWCQ=,tag:LdomInGvbX54AutRFxJdEQ==,type:str] -SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:u7mXp1lTtqGIx1M6HfJKGh+/S0nRWvrtAVJe4iBUdKvaWFW8/KFbjHOE6/RQSf6LTVVpdsfl63YVAVLlH2ghgydz3sh9hfALJQTiOP+0CjvjMKYhEo440zv6sEp5kjnJm9rac7DG8bYGjmrJCwMmmsS4xgXDboGgzH73gxukHodyUEL80LdmOQe4mbvHSHePcK+xWymhfI3n2XEQpFtlccIYYrqWFTUxGchF71OsplQxZ9nBoE5XG3VHqQ==,iv:GR5L7mr4dH2q2ldnpTjjJaEA/FeaUnZZoMhCaZd1SgU=,tag:ZI6OUhh61uq3CFvU3Wt0Yg==,type:str] -SENTRY_ORG=ENC[AES256_GCM,data:X2C1,iv:8yd1SEWhJaI6cekgE5Ia/J257OXB4qh7YTgRolFDpxQ=,tag:nO2NzUHCDecfpvrKEtE0Vg==,type:str] -SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:8no=,iv:Kl9l9odW748QyrJaryvDadS1Fkd1jEB3tFQmhBRHE6k=,tag:qVJDNkOMtpIzClxnd1wzLg==,type:str] -SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:qkAAX7U3crQ70/0/MaGOpHfPUH/w9w1fidjLelKIoikNzP0L6P309TQSLCbKp7VDptW7/N+Oi7pZTDohDp2ttod6C2jPp5IOVNWE7mRsEQ==,iv:mXHfd0fBQULasBu1rlxxdbqV/j5qrHMHHE60vPuiAhM=,tag:DD5QdKWP3xqZmPq99vA5UQ==,type:str] -SMTP_HOST=ENC[AES256_GCM,data:R4KgQe6/5afRANw/Kn1d4ZzY0s0HeyVWkGdx9lPPnhbIIQ==,iv:3I9jJLlj786XEUN5toTOlC2LmMC4hSK1FBBMouShBh8=,tag:KZEHGBB9wUlOKPjLCG+ZzA==,type:str] -SMTP_PASS=ENC[AES256_GCM,data:fnsP0UPfDLcpOjXl4LGeVKGo7RajA4d7HHNyYSc1c0WgcvX6DlOK7/25qRA=,iv:nsvsFK8QS5h809xUY+1s1SzcA36HQkDPbeHM7ClUqLU=,tag:duccNhq5B5gIJvuHZMakkg==,type:str] -SMTP_USER=ENC[AES256_GCM,data:yQorJa7k/UKc9AoQPcb1ckqdlt8=,iv:e0rsCh/UcNcfeQ8VKJ+uJwoyndieXc7a6+yawXMZrls=,tag:oX3nOk2EpYOV4ulcBALPPQ==,type:str] -ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:fkual9M7pkW4txUAltrX/tD68h8=,iv:1mPi+2+uYPl8H5N0UF534eVCs0dOAamSzm4NPQCPRjs=,tag:xQWloLt+CyDNBXlZVMLfdA==,type:str] -ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:CUjzaTb0VbjLeYVuy7KPHizZO0Q=,iv:7jE6QAtqysUaZReAzHRZ+8xxGJeV91drO5WwsGR4bXs=,tag:hNaHJ3r0GZp3w5u++mnoVg==,type:str] -#ENC[AES256_GCM,data:V7juNWxQv+I0TtmWJLnwUkOjx2k=,iv:cnoWnTmHi6M60F8ow+m9lBOyXFtI3oCN7VotEwVyQoU=,tag:fxVnVAswhFRdRiWcOf+3hA==,type:comment] -KF_AUTH_URL=ENC[AES256_GCM,data:qeMJ5xv54dvFedtgr3lV7K/m/lc4,iv:/4N/v3kQb+wv4PmOm77KbpexrUL0MJwO3817cGUOYw8=,tag:QiLkz5XtE1kQhBMqo+o2Qg==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:VLNknIRyka/w,iv:/xUAi1wHXKurI0S9ZTqQ+dr5GKHuEhEP1ef6IUXQfKY=,tag:mLTbofx/DA24V/o1vMlgEg==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:SGTsRe3QYFCcT5KwszFBBsC6VrKeHzTQakFYt2imMz0wuVWRsLjEMgfOKz4ZvLal0nBsxve3+8FyfJUSpVQcgQ==,iv:F6CPMbfUKMn7i3EnKtQ2HOKNiVFyfU+ANQ3EQNG3FN4=,tag:HCgyY7QtHpKjMn8OO2f6Qw==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:LDgrx6R5bsUTCAGHdVZtwT6wjebGqoJlo7lr/Q/dnFk=,iv:nIh12E7DPPY15Bc4/o63HnbbxFAGptahSlZoilr+zO0=,tag:vaTXyeG5ymBuQIwumIpqyQ==,type:str] -APP_URL=ENC[AES256_GCM,data:2tvfTs4bnVCU6XmIoPGdX8jcZfiW,iv:DImM0gX/v/EZXaH4ymBLh8UvSRWyoBkjzVqE+1dx/ic=,tag:+AOWRnr0NGHOAJfTPpr5UA==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwOUZ3eGZKN3RWNVB3c3FR\nRWxxeTUyVXV0UlZlZlVSWEQvNTFtelZFUGdZCm5Zc2prcGFFWjhieTZrYVAyQUN4\nUTB3dTFyQlVSOERDMWpLcWk4R0R2V0EKLS0tIDFGbTgzQkEzMnJuRXVrdWx5MDdx\nZEE2S1pXRmZzTDRBekw1TVFFb2IvM0UKZ39NqCi5GS7bBAnpTKeRw0LOrTt59xpd\n+LE7iOT/VMI/iw1SUTv95JyPjaNLK+Ve5b+niAyhWcV3rOO/Z5tUdw==\n-----END AGE ENCRYPTED FILE-----\n +AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:jSedz6UDHpTk1faKoJKFEVIIkIhX3KJc1/3l5AEPqPfuNN+5C8+PQqTYUENQQ8Vx9DqIVeK9KkUUSBgB/XEe+A==,iv:czCsVvUb1eD4eZq4k9ZcSbYKbryCLOYNLYQOc6NDpA0=,tag:+M+bdWK8tDXyV2Fd6VRsdg==,type:str] +ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:3hwgS/yhoJlWU4rB9XhZHAS5g1hpx4spbVR8L/b/NkSgAlnSScEIqIiKL2nkSMdkdZoUKbUWXhNOI0GwYkWYqQ==,iv:aqfix2BNDJl9m3QzWYSbSh4qIlFiFCodEnixm+humXc=,tag:kdsSoiae/y+SCHXEsgJEKQ==,type:str] +AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:pghnhjqo8hd3qNrpWbfCRFWxoTw=,iv:vznb6J2VN/W1lUoAvD/r6Z99hkzcJdem75wWiEW6d4s=,tag:UZTpPmIMgcaThABQLWz1rQ==,type:str] +AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:gnCEFsqMpAIVdIlF0BAWuxCFGWs=,iv:l2BVc+myiioI/XWZCxRiRFFs3Xg4g6jx4nA+ycUpEtw=,tag:Bal4LYtDSipG+Mahi3Oe4w==,type:str] +AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:2div+Eg4zDzDIPgLJtObkjka2JRKAsbWZPTRL0FZgfFYsAovx63Liw==,iv:dvyGJyuMgj5zQHzdmoTk1rrYKKxIo2+kQuFwbCTm5CY=,tag:WjkI3qAlFwA8MwrUFVyuww==,type:str] +AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:z46Hakr2vI5gvWFpaIAK3Ql3DIoCw/lL47j7cd0giSQTh/ABxsJ/JA==,iv:kGlx7FjwdJJckKOWnBEZjoytOsgzFytpuWc2y84DxOM=,tag:FvNS10I5wpTrs3CPR6AHwg==,type:str] +BACKUPS_SECRET=ENC[AES256_GCM,data:AlfLtRkXyDqqZfu6wohDkcWtT0V6JIoKJ3z2Vz5ZMupFUBC5D+14aLZAMzg=,iv:KWoi+M42Nw92Fqetpq5ofr2eyP4L0SSOOCqbX3SwBOo=,tag:zoocybd+rcwDGiMMSQbmSg==,type:str] +CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:yxgHI3i36FFS4L0TdzUN1bpXLIHpf2OEDCnUgD9XgDGOxf1z3OQu/3QViRmFeqRWcEtppvU=,iv:EIud/glaSfEoZf51OMOLkzBteSjDPmRoI8ivyoIFfZE=,tag:5PtJoBaPnEBtSzBhvAwgwA==,type:str] +CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:VS/NWdB8sMVehnPUZ3Udpw+aO3Jactk/3LHo04Z7l0IPq+0tVy6Nut4RQUFtqAgXyzob85o=,iv:6GEbd0TGl2eYst6IhB3dcoPaY5x+LxtYTp7LPFNgjmw=,tag:dYqoHnquH19vA7oZ8xEVqw==,type:str] +CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:/D8G2dWVZuLTAI0vfbL+PIDmhOvwMsuQOtimeBBK9nY=,iv:QReP2lYx4uUjQIfduUxdNq1u8ASOjvPFgtK86u+3MOc=,tag:RQWhCVs8KGdhE0yu8GD/zw==,type:str] +CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:TGAwib9icDVyKupEv1nm9PCNNwmm0mA825ZtUl6DwLg=,tag:Sal4TWsNght8fIHf7dienw==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:RUDE3AQcaV6KAn590pOwOl7qe87k5JP9URV0Zo8ASERD/Dka5nYxSwN/Dgc=,iv:z8K+k+RmM1dviYH4yGymshUh+RW89+65pi+kbNVUJuw=,tag:VjZrmiVBSUK1UuUv4Jz/Hw==,type:str] +DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:pmB/N3baHwHUvFsz7PriWNv1BtTGHAcRnbpmncjQJS19FA==,iv:cHr48cYEFnlsRs8gDXiKVmrjGETnizZEo08GLOypHrE=,tag:NWD4Cq5ZWRPqd+qxL1ZCdg==,type:str] +DOI_LOGIN_ID=ENC[AES256_GCM,data:CIIDza5/,iv:xuxBJyAT2bRzLsQVxPBZX2OiBt8U1UO0w5+vUEYRtDI=,tag:4rWD6V3RicNKvsJGwhhtrw==,type:str] +DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:vHEy/13TldjpwEDl6nqV4sq7VSU=,iv:WsGxcJfyuwBQBqR/YjhLJ5hW58OoQptTbvGH+vt1ba0=,tag:moDv//PwLAwjxCgq1b8Ohg==,type:str] +DOI_SUBMISSION_URL=ENC[AES256_GCM,data:fgWMFf/1ZqhdLUeBd+gEOHz3P26EpPU6oTTtzhK92nx5fO9K7XJf+0U=,iv:dqUlPW/NqK19Kmg4ojbg8sasJyJWxWqlGMPNdNguU7U=,tag:/l7OBHJMcFVVfJjpKKB4lw==,type:str] +FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:KdNtZ/QZfSrkEGbF2IO6JWAiSJx6b00mp/WnciZVWLk=,iv:04tjQifpUV5MBo6NJYNdVRS5/BVyhAl/L3gtpv6jGLI=,tag:oTos0XQAAlhjs76t1guGFQ==,type:str] +FASTLY_SERVICE_ID=ENC[AES256_GCM,data:YPXtwpSnGf/DbBnnRrmEP7aNc/G/zw==,iv:vxcyPgXXLe+NZS8v9o2JN1ORH/c+fEKrRkFschVQyvM=,tag:P4MnzQ58vJWIee9uP+xpJQ==,type:str] +FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:OcaGHN+EuI59SIKw4OZ2CrN+HC1lFjrHFbaEN6Hi5mY=,tag:hScOgvAJGVeSWhzDcltcFg==,type:str] +IS_DUQDUQ=ENC[AES256_GCM,data:O/gsEg==,iv:YNfBOpFYY7UMdOHpDf/WV34wQcvIxZK2YgQTKTwFZ7Q=,tag:FEbGo0u7Ym22lKUZEHL4mQ==,type:str] +JWT_SIGNING_SECRET=ENC[AES256_GCM,data:1XHMU1zNhoopCNSNWb7pZF+4mR3tdGiKEr0UU1P90i7TpiGMpKOfNrNrmIkk+udvSljHbDkIB41X9f0xmHvUk2wJ7nJikGHyUb5HRahWm4FJfbleec6mWn6xPJSV5Kx7Jfnbkhy2+aI5YkhLHed7sgiJEv+Q0aU1nyGsnS8zmOhgwgGp/UjwfNY2yaEs34tEWY71pTbaBOeS9VaX+Odeho4uvvSeNLcxrVDo7h/kXiPqMd+9Jdec/l+mIN5b/yQyCen2U441CyN3UwKjxIELdbocU5mslQ68y5As1r6km5sQkUfKUPF07iGGBeeTb0hqJQSJRQLN8gw4H45Ka5weolF/+Wz1Bl2IfQrcj7vZ3uVFiXfa96VryJCFCqDMX+jhUpS5BV0NrC4eusIdMODbRHRK3Wr5x+EkhS+BRGAf8rn3mp5Jux7KYBhg+KA1Bd4LduIqVU781HK6Nhw41palxv4IGZqn9HacUzdyise3KSPUVwtUGmXGi7s3/heBXkrXEDGXx0ACMWeWc7hKWC0ENSbmXm0hMk7VBtMscvH4t44BqZ+ECZEqqK321pal+dHoYeluXfMI4XU/ev6Ynd9qm9crZN3GHDZUM7wiYXXZPagn9sDosTKPbmOtw4jQn6hQE4aHQTgDf+WD0FUD6LUebhK1lP0HXKIxpir5lhOGccQSgUL4EMPXEmD+byEp5AyemmKy1xBzN8b0jUj+dtq1QtHaHosTDO9Cp7emuPA7e4bECeuVqBfwMDY8xMCFw7oOOqd03+8rNhGB67cEzx+ktijixXqvHJbBzzeK2D1LVl5GC5vdl2S53VzOyNtj5hUp4aZiYkJCooj/i5zEo5buAei8Rzbh+ewczzx4FK70qjByS+gdyxoJqDh7NE2i5QbgyeY/Rq8PNGmAV2TD/IyXTJx57TPoEoJkZmxkjqbjjRGNgEsdyl//t+rttlScsmjQ7qVLvaxNLl4JPYpOUPOWRNMgrUryDeWg3Z/EnJKBA7JJdbs+S9XanqFthESIDWN61tw597UaUXfuYSr/Ffv/+pq+OS7I/bRJMRZZip6cJC1dGpUt2M6pL5I6R2wgiinWCKUgZB0N20ozeZsOIUOF2uBQLEsyiLAoRaZ1U/QVCJog+meDiSh7zE6E+V4bvMnfE74ICDIlCl5HvCkr/6SFKkGT2yJjRI4fIlX+dt0ZNaqVzBt20vX+clGzLhEYdbh1J8pRQq1zm/pV3uoQBpqDt5TN0Q+edUAJKYF2+LepwVAG93/WdHkwP6mJJckU8v4TviIODUrI804JtUCb0SvoYRAb2j+LK4LZq4wIivoYovrBssJVEaTGKzmiR5PtZcsWD84dG5lbI6qk5fJY3K5AaQ==,iv:3mbAXwl4QGmjHGU4aB9QUlpt9KvQKsSWYMpaXi9ysW4=,tag:JjU4qZyuHK3Q1HKNjoEjsQ==,type:str] +MAILCHIMP_API_KEY=ENC[AES256_GCM,data:gOuM/Prm9bwa3yXobLCTNsCKbZ3jO/KWk+cCvMuQuNGclrS8,iv:7EBEv3SyFa3xs0J3wIHrwbIwSd1bejq+5wOS4Ays4lY=,tag:729R4cKLmAuml/hy+x5Gjw==,type:str] +MAILGUN_API_KEY=ENC[AES256_GCM,data:GnxtESWgeGP2MjQsjFQHQH5Snq2t4/G9caCpRXaBUoOO3Eyp,iv:se/jsTlvXSV2DuKVknXVZXtO6lsySj7zvzcmzHMFFGo=,tag:e2ec7U2dT+i40vNxkYL5yw==,type:str] +NODE_ENV=ENC[AES256_GCM,data:iLFf6vCev5kiEw==,iv:05NnJOb8WPCGiPOXzkZQyodMi1omKHbNemOgjZ8ZpQE=,tag:uNj+7bCOmGO8D4Y2V3PiUg==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:PVyNAr6WAKO4js1Ocv78iFU2jsM=,iv:mQb6jYNoJbtPqI8K9V11dfiqyBgXCtBrNlF2S0f8DzI=,tag:RUNh9eIltlh2Cpk576Js4Q==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:4sAN2LhI2mbaH+g=,iv:2zUntfNLSgclMPr2eeJZzxbRmCUDcn5nqaXEGQ/f9XM=,tag:rcll/08aCIB7SK0Z8m7bQw==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:96eTnbhuGGWxzL6ykeNsayHyQvNqtCt+UGTuGJMOFkt2/Lw=,iv:qwevMnwR+vakTJW/TpXPRPDRevZSnLEpXrdwcG1GodI=,tag:VW05PcHalxwiRTOLc3yMKg==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:qfQISkTnYWCbTVcJaIyy4hlkd+9wuxqxbfrcvbz41Lqv3yMBMaeptg==,iv:oEHr5Rb3lffBabX7cFyptQjOAtvtGWHXVWyw3/YpESI=,tag:OPWBEnr+EeWCvUE2/QTHQw==,type:str] +SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:GLarhotVHAlZbhK8cvnU+8VSt5wisz73FWCVktzil3BxhsxQ2KOzwzbPtHWa1UvTwy0rcP2LZdsnV7V2c5k7VzASlfC3Z8LS3HxQNv/78p3IIv3ywQ0owfV26s4OVvwBpMt58xH1Wx7vjnI8m/MuSYZRS7CxKuJIcL2HEnpIU6fUpLxvaAoffvOz1WG+HysCh0A3tiGDTV/bDwt9KBqOOxNY+qYmgYY8O0ccPNfWdHpusbYhuFGlJz79Zw==,iv:hg0In5HCufBdHEofe6gc6gUbrI2hd2TJfG9xlDkYgYI=,tag:/58RUX2CsLssG4Yd8GoSRQ==,type:str] +SENTRY_ORG=ENC[AES256_GCM,data:KnMS,iv:LoXVVgSgi8yxtEcAiILZoOW20DRn0YhhjTn7JfW0nnI=,tag:JUaJD3hOyGzQMrwABfVdsA==,type:str] +SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:ppc=,iv:+aCiASdlFM3P9oSx1r3rTdusdagt0wmFN8/3hNtO680=,tag:7y5KrA+iq5GDUZ+JnrW9cg==,type:str] +SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:F/ei0MQ6i9fYmFdiD4JgBxoVhB6y80zWOM36zHSrb+dYD6pHWp6eS02TzjbxtpzS5g2vhBKSl9mOD4L6q5gMRLQYQiXCIQZWqtXCEfUNSQ==,iv:N/3PyzJu4d8crIwUj/pNj851KTfV0Oyjaw8W7DnEjAo=,tag:qQt7chkB4M5aoMypmSJqig==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:gVTlsn0G+AhFLFOxLxK8oMKYWe96NdlIOpC0DwVTe84ZTQ==,iv:srScSfqsWzn4dgbgKYd9z3SqxR+ymRZE//gBzX+73fw=,tag:0ZXFNbDQ0tKw1LlYspet+g==,type:str] +SMTP_PASS=ENC[AES256_GCM,data:56wzIiVMlBllselrpby/wZzD9sUQmlcRWYhOMgpbOeH/kbmfBtHJvFXbGI8=,iv:1YIdyFiy00wp58iLGoQOFO+JcTTCkuvGDgzB7nhvaCk=,tag:vREQ3leI5cBFILpCuTXSGg==,type:str] +SMTP_USER=ENC[AES256_GCM,data:VIpWGzlKNb/lcCiSjiJtd7vOMtU=,iv:JfyqNy1ipaHSt87N9uC1JfbJDpKr+PscOOpcjdZUpo0=,tag:7KcTBnoZeARwLm0I7E7cuw==,type:str] +ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:rx9rzHedBMIS4/GAjmjCXwCz3h8=,iv:XhnqcyHNRYBvx7+DZpc43k8DzM/VvztnziKW8dl2630=,tag:r+tWGKa8o18umkwuRbbV+Q==,type:str] +ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:feog2Hy8yngxKnbfwhP4VNz4YmM=,iv:VLBtulj7qymav2/4kUiuNja4tc2kBWPbuWWEE0qfmtk=,tag:sd680z4kuNk8mUjzIzM4Ew==,type:str] +#ENC[AES256_GCM,data:9r89kx3dfaK6GdKZVg7Y0OhI4u8=,iv:QX8i7/6PB4kfnUB5lPnbZw2pUOn1nIw2vHvhIxOb1kA=,tag:74LfENAk3IvQECE0ps6x7g==,type:comment] +KF_AUTH_URL=ENC[AES256_GCM,data:UV18YeLuWs0PYNp5oACK3wRqKIJs,iv:2uazkMr4+AGIWBqhzvEitAqCQqHPwdcnd3m23L9m/C4=,tag:fQU0BPaBnKiqQVbeUIe3Bw==,type:str] +KF_AUTH_INTERNAL_URL=ENC[AES256_GCM,data:uIJnZqR+IK66B+7k3xuiio59uk24FF+5Y0FdDRvG/fE=,iv:NEAfay/7ImliuCgB7PPNh4vPRWzbMdf+41YZtip+H5Y=,tag:Nl3uCR/QjyGFnhWG2Ms5LQ==,type:str] +KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:qRkkoQHy+DX7,iv:1jhXxKMGenzoWFRejJH9T0IzDkezzCc21STDJcM8ssw=,tag:H/tCmD9m30n9eVUC2h3bNA==,type:str] +KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:B8bo7z4N0zbPCQ7+1TqM3OWJLXmFOkYotnzMSiDCLIGFysrLpUag20aQvqJAz76AwEbryHUNh936MbmcXCJ3sg==,iv:6YL0UjxSY6hbciHofFAIOFfubPnR3WGyYQiKwLxllP4=,tag:7JjW5uy1AnYPqXKQUJLzEA==,type:str] +KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:fCvQg2WVAYFSRWKZIECDN3Sy4L7Ki7vYmlkDixuwrUA=,iv:WOyPgNVagUqxBRW6ouDd0F57CJx7e4Tyyqahl+3if7o=,tag:Ztg2c7TSk1bT+g4blHJ8uA==,type:str] +APP_URL=ENC[AES256_GCM,data:uCl5JwHh/O7v6J+erNQKkfrzpoua,iv:lWu9gDqOhEuiQDCWCr2Ku0Irsn8JMz+VpLWmtR8l7HI=,tag:Z6477YmpoC0AuEZr8ipupg==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLbXY1ZVFLczFrK1JoaEpn\nZ0tDUXNBQWJQWTVSUjlCMmZyTXhDT2NiMEdJCjlDMFhJUVRhWjI4WVEvalRCcW5W\nUHAzYktPbEtLK2N2Z0NMc3Bwakd6WUEKLS0tIFVjNWxabTFTUEg0UFBvNWdtUEFz\nM24xNFZ3SU5RMmlOd3k1VzBXY09haG8KOa2DnFK2pCqALNx6Qmxe6mvHqVTJlnoC\nffAqsrvuyyTB3UhmjfP1F2WSJWRhxkvzFuOoNJvKfTKoIVojFlfuhQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNdmMxSWlyYUFLZm0xWjBj\ndjB5MFFROHdad3ovVEFobVFNc3d3L1VCaGlrCmw4U3I5QkFRcjVoRFpCd1F1ZFpl\nYUhTSmZtbDUwK2Ewci90VG44cFlsZ3cKLS0tIEU1NnFLdTJ5Tk1UOUJRS0tLU01O\nZE5oYWROa0pPa1hHY3I1VDJwSk5kZVEKZbKKwgkCtQZ8z/HUFjN9oRZYo21qbi4R\nRMSpWxHizxEEt3VUdJ4lWaBDqeuc+b4YhS3vYW+6Z078Vuhc735qag==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWGxwMi9FUzkyQU1OemVx\nYnlSL2tSdm91WFpzVHYxV0lkTitsbFV1Ylg0CnAzVzJaZytDM2E1MnNoUDdiWXZy\nM0c3NE9nK1pXTTBvNCtqOW8vU2RNMFkKLS0tIFVnTU5tMkdNa3JoRmgwaWhNQVUw\nYXBTRTlhRk5YV1ZnQ2syMHVZVnl2ek0KPy9O6dvlcWbkdnXVeR7z0pON9dLqXuPh\nSg4LyILguj83IGChxv5ijA4gsf+6FK1fB1597rwE02FWek/wFHRrEA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5alcvZVdKMmZWZE51bFlP\nVndoMlp2VURUd00yYVRKQ25Kci9uUkJpZnpBCm83OSt2WGRzaHpRTlhwb2xDSDRG\nWFBnM0luWnNMRTI5d0M2Z21WNUpveFEKLS0tIHNyZFZQdW1WdmVaSkFXV21oRTJE\nVEJXQWZaeHRqeE5ncUZCODZhTU9FY2MKcXs55f9lmq8thCt6XtdR4pPGBIM0nOhs\nyb/hxCd5dSnXxpwh1oV45pyFqy44GiEL0Qzccl2VsQsTbvSOgVgacQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUdmR6alcxTGExSUJSUVVE\nRUliQVFVOW5TaXNaZUgrU2Z3Y2swZ2VxM1djCnNhTVhjMFJtclpiQXFCRVZjckl6\nQURZOTZienVlTldhak5lTCtVcGQxNjQKLS0tIGZ3Rk5GZDBYcVBMbmVVQkgvd1R0\nQk5zem5COUJvTG02TDAwZTBvNFJIMFEKVSk4/xkCx2XtGVINAGKd305+C3f+7KJh\nF/k5Rg5YxrQoKVpfN8npC6CM6iW7TbIeN6q3A4wpIrym3SwRXJRA3w==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZL2hHdk9PK05iS2c3Ull4\nL0x2SXl5OG91QzlwN25DZ3NnU1JYTmJtUFRJCm9OSVJkNldPRnY2VXRBZE5UNjMz\ncFl6TVpDNFFiR1JPYW12dVc5aFpnb28KLS0tIFNkekhwL3B4RzNOV2hveXI2SHZ5\naE9BM3pLMlEweEZLa2dCV3J1eDh2VnMKp/NkvwDan14XxLtX91U60uQ7r/OuUySC\nAoRmRhOVBOwFhtC/06rdL8wf9AM7TPKV9uwMuIopNW2qFovs8x23Sw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXOTVKRmpSNW9lYmdlS2dE\nWmxrcklMbWJrMnY5VFhKY1dSU1dLQkxvVlNRCitaWlloRGM2Q0ZEa3JleEpLaEww\nU3JQbDJYRjJZK0U2MUJmOGFWRXNjMXMKLS0tIGcvQWVaMFZDVG9XSFRERURJcVpv\nc0tpQm9IQWFoNitxejFXaFNaOExxSUUKs9CvxdSQCTgT4CqoA3A2E34PKpvSqkhT\nWl3wB7VfX7gW0yKnTZYWwT0oHDi8ihXk6wU6mgI83h1s0pZgItSZgQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqb0VsSExNRG9ONlZlb0Vy\nam9jdDFqamN2Q0YybTBKK0tqQVM0NU9kUWdjCjJVZk9hb1lwdHNaQ051QjVLU1Qr\nMFR4bXZCaFptMkY3ZzhEdk9hVytXVjgKLS0tIFB5UnAzWnhOcGtEMkVyd2ZxZVBS\nZlJzVDFSTXFxc2RSWk5BWk81N2MxajQKIGxV8cHYxKmZ79Fp7r4RMt8FJ93znZGo\nmBuEvkO/M2EctnjO+SQDQIbhNpx/G3++3LmrdLShImQ+fK/lDdHNfw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXQ2RmMGJYZThRTG9DWHM0\nbnMwWUNReEVvNHZQVkY4bHpYMEJmTVlYZkNjClQrVHl2anZRK3Jxb2crT3ZMVWJz\nbzFoT1dHSzk1TE5nWjBuRk8xZ2hZdDAKLS0tIE13U1h6QzAxV0pzNU94RCtRV2Iv\nbE9xMk9NVHY1cndUcmdYK0tNcDQycHcK+52eaN10NAi0zGLjQwJCgxdolSgBH6HG\nGZQs75Vi4y/0gOcE3cL2aqPYMlCrVdgazLODEhn8qoDFjTvYSqwSFg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnNlpDaUNGMDlycXZRUG0v\naldWSUp6WGwxU2lVUnFwcndCMit6eGJSTkc4Cm5MbE9uejdtWGVLUmgwZ2VwWlcr\nbHQyRWVldm1CVWVjZk5sckhYZ3ZrVTAKLS0tIHRDbXZOa0RXZERNZFMraVptTlpO\ndU5yeEFwelBPeDlReUYrSStWZDlEQWsKQ711aKw9Jbcq88IczNO/1esls+JRaV1S\nPIyPCTL2YTuO3SSpHZIq1OjmHhNmDN9nEQ8sw8Hg2FmHvPEfozrxZw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1Z2pNaEhFdGw1enZwK1BL\nWXNHN1VvdlFKakJKVGVMNjdrVE9tcTFwV1hvCjgrZHNpclFaMU1JdWlvWWNXZ3JF\nKzhGcHpVTlUvcnozVEUwdEZ4cGtTYVEKLS0tIHVmMlpvbm1KRGNUbTRZd3gzaTlP\nNlVka3pFVVJST1FVNTF0WDUvL1lHaVUKu/p2zf717p95Qwpsy0DFFzFki3Tj+T3V\nKCb0qMq+uhq/1wzOXNp15oqWmYHWeEbxzOiOuAPGajd8bv7+CyMQMg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_lastmodified=2026-05-17T02:34:04Z -sops_mac=ENC[AES256_GCM,data:h/n+dajxY6dwasJsndS96YBIdlMX07zbF6gC1Ecealo2Zgn7YQDT7W9hYebJiClf338j87x9NSHbDL5m/TUxMfKgANziXViP/7EzBMpoEEMhQMXwXz/Ul6sRYfByMZI0kDyiZy8FiMX+hwc2elKZdI1cKcJST+VCnLBNZbHaBz4=,iv:OqlNfTwi/w976o43EOE/ZLvIYSodumuhfyPpVPrlYY4=,tag:qrPSlr8rB17wV+wn8GYzoQ==,type:str] +sops_lastmodified=2026-05-18T03:04:04Z +sops_mac=ENC[AES256_GCM,data:hxv3pWG3ojHyAo1vVf17zvUtT28Jzqt2aEaE8bmqeDoXe/Vhq4SgU3OejmCBUMP7mkEMDjSTpixqA0yeXsHlF9bWCWvlCTQGCGpwiQffJbu7u1llajqHjobpEHdbFtgLHA/T2zKpAKoknER/VTn6IzKdzWVkFv6Sg+LRJhH17ig=,iv:iNqN1A5VBVCBsze1ZN2ba3V01esd7MUiALs1gcPWp9E=,tag:Ov3Kz+aqOMcT8NjwnyrXrg==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/server/kf/auth.ts b/server/kf/auth.ts index be1730417..17f32ac5f 100644 --- a/server/kf/auth.ts +++ b/server/kf/auth.ts @@ -1,13 +1,16 @@ /** * Lightweight OIDC client for KF Auth (PubPub edition). * - * KF_AUTH_URL is used for both browser redirects and server-side calls - * (token exchange, userinfo). + * Two base URLs: + * KF_AUTH_URL — browser-facing (e.g. localhost:3000) + * KF_AUTH_INTERNAL_URL — server-to-server (e.g. host.docker.internal:3000 in Docker) + * Falls back to KF_AUTH_URL when not set (production). */ import crypto from 'node:crypto'; const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000'; +const KF_AUTH_INTERNAL_URL = process.env.KF_AUTH_INTERNAL_URL ?? KF_AUTH_URL; const KF_AUTH_CLIENT_ID = process.env.KF_AUTH_CLIENT_ID ?? 'kf_pubpub'; const KF_AUTH_CLIENT_SECRET = process.env.KF_AUTH_CLIENT_SECRET ?? ''; const APP_URL = process.env.APP_URL ?? 'http://localhost:9876'; @@ -115,7 +118,7 @@ export async function exchangeCode(code: string, codeVerifier: string): Promise< code_verifier: codeVerifier, }); - const res = await fetch(`${KF_AUTH_URL}${TOKEN_PATH}`, { + const res = await fetch(`${KF_AUTH_INTERNAL_URL}${TOKEN_PATH}`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, @@ -150,7 +153,7 @@ export interface KFUserInfo { } export async function fetchUserInfo(accessToken: string): Promise { - const res = await fetch(`${KF_AUTH_URL}${USERINFO_PATH}`, { + const res = await fetch(`${KF_AUTH_INTERNAL_URL}${USERINFO_PATH}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); @@ -169,7 +172,7 @@ export async function fetchUserOrgs(userId: string): Promise { const key = process.env.KF_INTERNAL_API_KEY; if (!key) return []; - const res = await fetch(`${KF_AUTH_URL}/api/internal/users/${userId}/orgs`, { + const res = await fetch(`${KF_AUTH_INTERNAL_URL}/api/internal/users/${userId}/orgs`, { headers: { Authorization: `Bearer ${key}` }, }); From b65cf1ce99d0c303f5310fef417c7702cb79f404 Mon Sep 17 00:00:00 2001 From: Travis Date: Wed, 20 May 2026 16:45:55 -0400 Subject: [PATCH 14/16] Generic OIDC client with discovery (#3616) --- infra/.env.dev.enc | 103 +++++++------- infra/.env.enc | 99 +++++++------- infra/.env.local.enc | 105 +++++++------- server/kf/api.ts | 4 +- server/kf/auth.ts | 202 +++------------------------ server/kf/oidc.server.ts | 287 +++++++++++++++++++++++++++++++++++++++ server/server.ts | 7 + 7 files changed, 475 insertions(+), 332 deletions(-) create mode 100644 server/kf/oidc.server.ts diff --git a/infra/.env.dev.enc b/infra/.env.dev.enc index 56a947e43..11bb2fb24 100644 --- a/infra/.env.dev.enc +++ b/infra/.env.dev.enc @@ -1,59 +1,60 @@ -AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:0/pThcyBgD71JouwgwyrGv4WpahqQdOOLmttTM86s+YGgimz70ah/MFhPdsDFJJWumb/TPWxX/VeP6NsbfV9FQ==,iv:6MDo+RbTa8NBQgC0fCWYeTy6BJV2A2rQHdDjUEn02N0=,tag:4kV5AVKtr7LlN9sWWxXBlA==,type:str] -ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:68lcVheah2pt9Nbir4yaUq6ocmDBd/BEY1P3DQQc6mNMjYIEhXnA5SB/DEismNpCL3VCQWNylDVHXsILJLvOxQ==,iv:tDx7V9M3ci7t8buYjvMhkfJq+wFiEgKzLGP2Dg4mHS8=,tag:cblmvhpbTd2mU30oIQ4gKQ==,type:str] -AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:+a//C1kWWbLomU2p7rYX5u4rkAs=,iv:oIoKmSZ2yTY8mndEOmyqBfAdafoIDH/8LKkG8kABQMw=,tag:nGjdlaF1l34lxqOWZpAHPg==,type:str] -AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:NtmyfFACf+Xu1AvD9loJp035oio=,iv:lz0uz2ycbWhkabM0IF+NJ5HhlelkZ/eNfK8sLW7dVBw=,tag:QBkvDiEFbvEMYDzP0JjRBA==,type:str] -AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:AX8joyH10Ub+N+NDAyLpH2EedX9mE3A/6TNKOe+P86g6c6OnWAnPaQ==,iv:dR5DYNDvwZU+n8k/Lr6xpOUiXc3QQ2kIX6q3Tqspva8=,tag:MyYLKYDJZ3fhacH92Q794w==,type:str] -AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:QXj9kz2WoScLvEj/3IkatJlbicQpSAwU7E86jV2icbSOys1iigzKrQ==,iv:lCLu4jzI7XqrIwxcwlcMdBpkwc41R4e+i0CiH/ZPUC0=,tag:16fNUIHWScxkhB6Gh5soew==,type:str] -BACKUPS_SECRET=ENC[AES256_GCM,data:5XW1JuOwA6QMhFDb3HAsC3VvAAfRrpZJtyo3MfywxF9/xLfSsB1qtSA8kkQ=,iv:treasNyESOYrObwki6S/3VtaedqGEeuWiwtRFqHJhpo=,tag:XsWwzPgkm5xs4sZ3Ao2U8A==,type:str] -CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:rQ1Mn3NjQ4Uz0ThThWZQcfsdvYF5w/oijEiISguRXmQ5dSXfgRPJZNk7THkiRCt/WQpmOpw=,iv:Fkl134fgT0Y1js7NJAg2DiMVHO5EIjLP+4e35nva/ls=,tag:RRJ9XeCFRioJSopuWO+5Bw==,type:str] -CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:MbQ0eYpUzwqsF0/JQrNI0ifrwKxdqFjkAouuq5q2AZFyKkkDeQ7PbyunOcbnxyM9BXajekg=,iv:MT5nY39XTrdo06Qg8R2zuwX/Pj8cIQ2BF1HrL1HWg3o=,tag:3ZsabAWhA4mNvJDqApf+MA==,type:str] -CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:T0eYzCGVinoAShLqgPsW85b4gFBBxnLE0PmS5RKdUks=,iv:n+V8Ct0R4Mw1D22/YEwjB91eLnoIro3/UjyEg6dIhmw=,tag:IV7PLYkwa9RgGO98vZkUSw==,type:str] -CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:hdYZ5t2pU2BtIF7KSkOgdq7S3myDPI9Hx4p4fqTY++Y=,tag:YV0Jdn3cGAXnfDFe4vu2sA==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:GyGXrI48jnJ86TcdsZGucGtPcbMgd2nKh8+FxNd3MEfVpbXw4k7v/OQv5I8=,iv:B8PzKy2Vs3G0VPtexyGTZcJPobgVsAi+tgz3zYjIjc8=,tag:Wnu/vP2aY19efDSdm973rw==,type:str] -DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:1MwvuSNog94kdsVmWzkdmVg+aabVMNPT/o5I2HBL11SBSA==,iv:ocvKFMH+WNParAfV07UP0DDkRC4CCW00CIkRhP+IU+A=,tag:eerYbjt0k/mPUm9s42WYwg==,type:str] -DOI_LOGIN_ID=ENC[AES256_GCM,data:Ocyz8s1k,iv:vut+asYLKbSnxHY23yqIb2eBuYpE5JqGa/ZUvxJwysE=,tag:8i4+DdWNu+f/Mi740d8rYg==,type:str] -DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:Q3Z1IamNafwJ2r94e0qccqbGJ4U=,iv:ezsIfdXsL1zrcai+pYrdCifKP2fbaUo6/mj+ZGsRLKk=,tag:MR5KmCPlT6JDcflnhWk5bw==,type:str] -DOI_SUBMISSION_URL=ENC[AES256_GCM,data:pi+uH+cznIJP80v1aOG2MLj3o3V+blM4cMH6kaNL3hEeipfInMAMf+A=,iv:0HA7zy8zJ5Fjvg3YyUWwE5d9pe1d4J21qU1vPW0whn0=,tag:1K8N8x1g263FL/4JO5oOfQ==,type:str] -FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:KiEKh/s3uRZmtaoXNNpihnm4bj9wAC0PeLbk1U2V1uY=,iv:UWWrQYLqVucyts0yiNFoP/GksRKboSLHfV3N4xv6wiM=,tag:N1d1eoA8rW5Sjlt+sXzjHQ==,type:str] -FASTLY_SERVICE_ID=ENC[AES256_GCM,data:M1VmWapcad3acXAUX2Hibm0IdjXG9w==,iv:0bUgfSHGlAKVA1pXjdNdIy0ZlZ3wR5TyWPrGolVVcPs=,tag:rHRkh3P2gISFlNooVt/kDg==,type:str] -FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:LVt3Bb5jVgVy6ham5LyIBcqxyfD9x6GtYgXfrqkEktM=,tag:0e4k1QJjf8Mp1PVplP+uOw==,type:str] -IS_DUQDUQ=ENC[AES256_GCM,data:u2MRZg==,iv:PO7Ra8bZIwtV9/1XZc4XV0oh4uPxmnVya/PiHpm5mq4=,tag:4etbO8o2OfJhkL+g1lt8oQ==,type:str] -JWT_SIGNING_SECRET=ENC[AES256_GCM,data:GRq9iWaZffs7+ROtfQ4M81nNXktRXYwNs9UPzWkZ4OgE7y7WqUA2ZvVIb4wMecfgAnQkqC0tydnOyhQ3P1V9W/tSov/4p/KLZ8wTNV5wuiUk7rIJ2UNkpR5QWZUavTm9QYVKEkdAq5MvJJi1ByJJ2MLclOMX8wB87l5+X6NWwwv935Oo9+148ikwhjvs5IRgy6exaJsktdNCZqGTP1U3Noz1j/3onaOTGH/rGJTPNj4AjBfWZDJF5qlvVIcvLqwBXI4dp9yicQErhafRCa+SOGzvphdvzf9VV2vq9IwQoBL1NnyIVttTdbbcx9CHDDZljSBxcQixHCxw8lVbakEhY3lb9ARiZcxh2tFkrdorrxeNPhwUtsTjkf8U7S/WTJnSsp+sqdaMMjbhWm0WJxrZ/wF0dj60jfxp5kT3EjpVqnjezTRkeSvn+EyMvH5R0SGQABeXaMzWE1dgUcVL2wCymtjw+sluxo+J+kxHhsFADaj6sP94pLBdoN2cjg5jAgtsfgPYNvS3P4Jy6OBDaQd3YLfNcoFbmP4jMGVb6O7hx75jixe+oGSYIRskk2k02b6i4rtQznS7kqYDGVD4L85QgNIQew4XMPVT7jc/gWC28FNt/MuhinaiMt2lQSlNYvCsm4YcDuTMZXZjbMIBUXUVrLhAhMkyTjZQ9ZzBSSX7pufbkYy5TqUfejPKD2F+Bh91zrFntNdxMNbleJb581PQuQEPkmVTe/FENnOlQKlua70BsSqegVbff0y+uU4Z8H7tYGDgo9TqiPQPbtNSEjloby6adeZ5dd+lyL6a+b3kmVncPzHwdb+5pVTzm1+UOrarMmX4qXFCvCNJiSsJN3bUf544frCHcacCujVfKCVekJT7VrSEv79M2aei8D0ysRCK/Sb7o9P99zrcppYdykvprKKJHhE7ZndICUdx9kxR3+VgMySgTLxFjPljIezKIzbLlrYrYiNQv7/pJMuOYlKX1eLhKdQO8MSt8H1Ue4dB8GUMpj4DRfB9fMhVmXsi2HivzVxksXrSyDC/ifLCJZstP2P2Y/My1HhWQuqE3t+VSE8699k5RpmaXzKxy7qzxulQot7r/hendOkWbIHnezP70nBZU/lqnszgGoMSg0PsZmtuas1e6si9L4hMXYCtnXA01j1y0tbGHzlj9A+5h7wuhIVJb3jIaBaOXgHQbcK0bvh/OTOv5eH4+lgW3w6I1AY7PpH2u0+GxJXP3Ro8BRGvoMTf5vTC7goF8thbZ5nMAPVh4MSDFbOaNKjJoZ2pSQYXJO5xBZHF8cqErNXCPuf9FZIOh4E39scfBe+LFv3cv+R0MZGwMNB1lL2fvPQJ4oyuesSubmEHhUKlSrk1Zcm98w==,iv:9fRAfd2+6XMI0PMZxDr4mp1ThSQEucVkql2OVxD2R3E=,tag:jI6s2ypxFiaBmjPVrdIO5A==,type:str] -MAILCHIMP_API_KEY=ENC[AES256_GCM,data:jF6R33ZGlXhAFAtOI0oYSLXqLFNKtZmACvIn0QJDe/PjsShT,iv:uVPL04rmnBS1ahAiWSWtXnLkijxFQ6jgSux92cO7M0g=,tag:Tq0uTnH1PWgXWnod19u21Q==,type:str] -MAILGUN_API_KEY=ENC[AES256_GCM,data:8PZlwqWZuejs3bCd8CUQAfISkNDdNl57Uj/ZpRSuSDcnFLAU,iv:63ToWd6DHBEB0DsFyd5sZiqJJTdrOlqab+gwtZl9/JQ=,tag:hkYg6BdJy4uf2T7ewTVX6Q==,type:str] -NODE_ENV=ENC[AES256_GCM,data:hlr1cwV1vwwpwg==,iv:NZV8TgQe1SAIjBmysz/d2w+eyKI4kC8nWLMtZw9Kssw=,tag:6AOb8S6Dv8vyx0caYojDCw==,type:str] -S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:vyKU23+grCh8wJWiDWEZ9eVWl6E=,iv:mz3oPKUjTBicxWDJ0BhssfyYDp31dcMxPZh8uzwGiXM=,tag:O2L4/z13JUrikRdWDshCow==,type:str] -S3_BACKUP_BUCKET=ENC[AES256_GCM,data:n41bjhL7Yvzd91w=,iv:ZUMCmYQe/Uf5ioA8/knN0B1B38feD36V074uaQnS5Ns=,tag:i5pQHk7PC8moP+jt1wEebA==,type:str] -S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:xZB7IA8mLlx9Yewnfc6hN533HlONjtO0Y8bZ5IfJO65PWPo=,iv:j5ktjZUdrQ8G7xezU52WhQPH+Lz7b8HtvYFFvOM7rBQ=,tag:qOr3T14vn3fAuJU/i7LQOA==,type:str] -S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:6xpkj2ofmkrrfih1TlxCUIsvtGoDYYFKbmvx+RBtx+80PVDZAcgGaw==,iv:hVppfnf0JHUhhX2U6pCZeBa+Ps/zLmhhcflLv36ZwUQ=,tag:m9vYdOnPya+0BVpu9s5isQ==,type:str] -SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:z6ipQkDdq9GbJxR72urZ8mIzLUKg7Q6DUmhpPtwr1on4gm0RpNRx6Z8pl44O1zEW2uE8EyHAoGMUmfIQa9B4W246S6xC8rqYVDrwyeQpdU64UiPW1UGokei33XcfMVhsKXcXcmbs87p25+gzn+UsV4ofu/rKH/Qls+Wdrsg2vNdaUCrmJKnsDDESfEBs4ym4r1BaByRke6hiKUYZObv+4s62zh01sUtVz1AlVDsG2bZP//GrEaCqRWM+DQ==,iv:WRAMvRFdKMvtsS+uy/fRbwdW71nI9kUt8szDMQCgJeQ=,tag:tcqnU2rdhJAS7Nyw5UwIog==,type:str] -SENTRY_ORG=ENC[AES256_GCM,data:W1Oi,iv:n9GFTs+0EwIA/gCdrgRnlXito1Zvy6i0hdbOzOU/LPc=,tag:C0zE1Awo82BjHDQ70ZxNIg==,type:str] -SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:fHE=,iv:MNRtxZZzKFnAHn8LLOiUKJbHekUcq8vNmDEHYBPYm+A=,tag:w27ZGhzAJa3fX7QQgw2c0Q==,type:str] -SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:RrqmKuvPRi3rSsubaz/9h2LdVoqSI1txmxHicswPgV/E/1mh2VZ3BuuvUGBds6gprbC18PgWjNnaZBGacMI6AvFNRucL01cG3euyGZKJyA==,iv:yN2LwK47xu97jxdYpu9IHrHkbuGesk3OPpBkkaaAtSk=,tag:FvGv0czDWdluoBdmlOLi8w==,type:str] -SMTP_HOST=ENC[AES256_GCM,data:tOPjXmNyn0q+kYtK4F5rg6etqk6hp5+yuc5+N03ObkRFgQ==,iv:WC2Ls3J3XzXdt9Djy0JnYlMNiaWT5Fp1IDXWzGWe8gc=,tag:tqI83ltJkYiCQF5UjCDSbg==,type:str] -SMTP_PASS=ENC[AES256_GCM,data:wXNewdWOxvaZ+s9LId5xzHJ6A5WQpRkTKzk7kBAEUAHAepY2iy1l7W62ePE=,iv:wCgt/HSwae8fuLnjSV7wS6/EWVw5XqNxbs6p9HaXthY=,tag:UHodP4PYlGNNU5RFhDOrsg==,type:str] -SMTP_USER=ENC[AES256_GCM,data:ZuX4HpPffhSZy1x3NrNMPK4CY7c=,iv:2zvisVfHxmZWMIY1S2jKq7mncbU0PbJmIS9QYBCNSdc=,tag:3Fd4HoKLpwNtMehHu1V0oA==,type:str] -ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:SGMj93kLneTedJhzzJLHCnoaSEU=,iv:r/1SdPDEcG5syCG8abaSBckSzlqQ8E+rAwqDNttoqyY=,tag:wa85xP4YWM+ix5Kn3ClJlg==,type:str] -ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:UL//USfKy+IgxEAtnCtx9JhJMVw=,iv:OUPQlDAOoqNMM2ZHdgdmcuP9twotPV+p6VrwaRJ3czk=,tag:gQUd0KEhoVbiMtonu2ceyw==,type:str] -#ENC[AES256_GCM,data:L2euC8ocqeH9DQ1Fgpm5Zv+6jAw=,iv:Y6siwsJ1bxXFubgpx1lpEL4xp6/GrGlSCnbAJKvMcwY=,tag:SoTTKg7MInia/fWY2SG0PQ==,type:comment] -KF_AUTH_URL=ENC[AES256_GCM,data:6sWKG13lAk4QFM8CQAeX/GSra+6tSZy6U2DDRg/utT86yeJVtA==,iv:5W3w81BiQjuDdV8Hu+UsDeQGQ4I2qCzINSgrw0CV88w=,tag:bNkbMnatnizs+dKaSOP2rQ==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:YJzzZFJlh0EE,iv:mAFUBC0UVhcv+CU6V6D9eREZWFq+xdKynKpIV1pMS6k=,tag:zU7037uBK3K6DEcrKyrSfg==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:MGFckQuY7PvDok4NC8uavb26yLeoPdw0pnCd/jujY5A3D2PXNME5I6xKwAVLve4jlxqlPgS1Pnuel7pEwG2eMw==,iv:4tHujDdiNKsvh76PRgbPm5i3k5yS5gi6/+xxrjmSs5U=,tag:qY/rCWsXjHZ3KbwIUf34HQ==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:ICCPMzS8V2CbcXWhv8bTN28IT3YbdCVuhkGB0L/TShFDxQvNuuCepXST8pyTQCWxzuiY44qINdwQiyf4P241zA==,iv:Yy7d3C4uid6x5vqA8cB3i+vjp68AdataVFq3slgKoL8=,tag:ZlmchFCXAXJc6wweoYirUg==,type:str] -APP_URL=ENC[AES256_GCM,data:fJOA0h8+Rgh6bcrWhAW8XqEwPkExyA==,iv:DUBOurc6b8WSbaXxNg7SAW8Bf7g1pfj/gAuepZzH2qc=,tag:JhmteJepQ6J6PzKUYZQFfg==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrTFM5bDlKNWZDUVozb20z\nMi9zUG9EK0JYdFZ6cDd1alVhMGd0a3Fma0NnCjQ1TlJkakpjMDdtUlRJUGJtVUtK\nNUIyMTNVaUgzdElVT0pKWnhwOUVzL0UKLS0tIFFYZ2huK3p2dHFaT2s2bm0vQVh4\nRlN0UVM0aFRzeXAyUkdHWU5URVJaZGcKfXe8NRpAOYM9PZ+uOqxS4dO27GXTc0K6\nH0j4kwyE0yOEay4e4G3AzcKjgrRmRyUs3R0Z9gM75FHH25liYXa1Cw==\n-----END AGE ENCRYPTED FILE-----\n +AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:QTHQKsrGXVcC6n0Df+FPitm4spVEFmor8tBW1Us4KecRHT/CS/c4liG/zhmxg608o1BImvYUoNXOnAUzx0c7rA==,iv:UyRQD8CY/wKgBvfVwT9A6KME2OS8qr/DaFByVw2hy40=,tag:tEAiJg8C3RQFzVbl/kof0A==,type:str] +ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:pEQuEORnajXVGVKBeGIybmceHBr94su8UjdRdxVDs/w5P2lA+AOkljqmf58pFp4QYljvb8WM0K0Qucu6BX6nsg==,iv:5JKeF6ZlnlL9QKtSZQvSA8owri690+LfqdjO8qH9h0k=,tag:hYnfaZdz2U1NZrOyGbAmRg==,type:str] +AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:Zh6nZj9vyGdL7YYyjZnV1Hu2fsk=,iv:vIN6uPJMx0+BDGcHe3tazIOwZdzeqYSznyxDWgPE7yU=,tag:AkTJteLfaO55PmCpFdDHGQ==,type:str] +AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:AukGF5oUW76exk8tlVSCK8C4EbY=,iv:uYhLhWI5MmLLYX+0tE7b6JLSUmVgfV2/M/Rc2dbJeO4=,tag:a0vAFyRyyb926Oq1UMIGaQ==,type:str] +AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:uM/KzblBwrJX9ixFEk3thYP2x/c2pn+GHU0SfkbvbNUo6bJQKPHUtw==,iv:Kl9+6SMhzqd0/FepOET/5wOlmFHG0NSl40hZ+xpc2cw=,tag:ce4G5Wj8IhLtqGH9j4yOgA==,type:str] +AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:3yhIGaVfcSx82Iu570lGQTj7wg6/U4eKLJBX3fWllFUXPQ7YTaTSxg==,iv:Zw789TR9JYE8Zub372k1+nUUHHp5898+leNGmUUadxQ=,tag:OdHy7ixCn9HgAcyOotFf1A==,type:str] +BACKUPS_SECRET=ENC[AES256_GCM,data:vW/KL6d2JrTLEqMyWdewdG5arNFi/k4XItSJIpKtLCubWtVPA7Vx+ndKafk=,iv:A08RonZOgfK5Rv+jFCrPulB5GLFtPWQhzWEo7qVI0H8=,tag:YRMdEGEXtHwbXYHOxGb2IQ==,type:str] +CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:2ycCWIKjallqDhI3tI7Y3DkJD64i6PpwTUkb+mTcFiho5W53mCjqX7LHoBQ/KM6//aHN7f8=,iv:14R0jm2nxMCd1pfRDah2n1b807opRio8/1oMqG6CBXc=,tag:uOH4SQRvgkW5wumxqQ1RfA==,type:str] +CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:aQjJLeWn2p0K5aMlGQezyhhnuvp8zIKCSdEzQRPq5y3vq5cRdrSijdLNl5PyFbyse3OdKW0=,iv:cY4t9ygDSouRuebY5sFQnOsFmMHD+STqiI5A7DOxpQw=,tag:CBEsbEuvR5sTy6iSboIfXQ==,type:str] +CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:G8HYYAD4o/9v2ewmQHLoyi7Gs/IC+rRfVbtrO7WG9lo=,iv:UU5q1PeCYI685vP0M0mPnfvUg1jEaJF29+n65YMGrms=,tag:PzupiZcYg+S60keNcbZscw==,type:str] +CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:mRgRYJVuUOeYwrCZbjZIVZzjNuWpTsLkNYpl0j/wZNE=,tag:kOtCOlJB3gKyzaZsUo1Y9A==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:oEBTHXU0LJ4AmF9ProfQDon5aGG4vDQK5JIC/o7OENh6Od9I/zRMaxOvyPM=,iv:EeYD+Z829jA7mGKL0HUDoMBbMTpxp2IaDm+Z+Ik98K0=,tag:NtmCFVY4OCmc73dKlWvtHQ==,type:str] +DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:4OWiB4JXhX2j/yk8YF//3yYLZpG2RC34yUJUeBi9ilNySQ==,iv:2TPp76R7IK2LxrFPTv1BbYIYEEf9oyZbY9fODp8negY=,tag:ei3JiVgIS44q06fvPIPioQ==,type:str] +DOI_LOGIN_ID=ENC[AES256_GCM,data:dBNBTJAj,iv:+L9bHQ0qpE30YIvkHhLy0F94xk4WltCQ0kiN1tm5MH8=,tag:42lv4Qc61GMca9SU22yPaQ==,type:str] +DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:pYL86VNuswtQyo5VTmDSBO8FWbo=,iv:eyDVpD/d9R/YqUDiAN823aRtWJSEIx71aZ5vzpSxMTo=,tag:3dWa2DwrxdeYfYxL4G84hA==,type:str] +DOI_SUBMISSION_URL=ENC[AES256_GCM,data:tUIkPyvU2TE3cWaGgDf+AFgzFgDibsgUUV2hNkdDvJxGYsAecNaq34o=,iv:SoWYYFQaI78/4KdwSu3Q70vYsTawiwv3rHDCPAN+J0g=,tag:u+4BZU0agtSkFHBPF3G76Q==,type:str] +FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:w24gixo2dL6Nly9Numyq79W1FkC7gog5QxIwb7r1iJw=,iv:NJXaDOiofZwIHtuFaFbAc1S/A7wsZURUt5DD89lvJIc=,tag:C4hoW3wSrcEFXrapr5UVQg==,type:str] +FASTLY_SERVICE_ID=ENC[AES256_GCM,data:km/VXm/CCP2VUJFkdrh/G/tr+A8VbA==,iv:x9vrwMvPg4eXUQB3MuzZ9Ra5jD3y4hJYHGl2vTWmyys=,tag:rXPz7yyBL1i7g3BNe14xbg==,type:str] +FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:t/EvVAQw2gbV7qwOZO+MlmKqCETbC7gqRsXrKxDvWUk=,tag:g68huzvs+cmz/jNKj786IQ==,type:str] +IS_DUQDUQ=ENC[AES256_GCM,data:+obHZg==,iv:L92l1JcMlAf3eBgrmn5ktSkRWAu80OsM25pWAwbCGz4=,tag:mgSQTM2cjEyoG5tVwqQQIw==,type:str] +JWT_SIGNING_SECRET=ENC[AES256_GCM,data:dkLpNzbugrM6XUEjI9PbmXqVDMB41fbL0BRCZ5Wh1/cnXqpAZg9X1+ql1qIAWl4vUdTT0Qla5hBvQS9AcTl2VrCsu3opJgZ9KfooMnO6fgeIcKn2jolWWOXgafKZ9OauXHUCCAyhZK2MHz7kY2NcaJHjqdE+gzec0BW4s+T+44iKeX9lWCueGD0Z0pomAfJCHVh5TOppxNase4rth48Fpl0b+jbIpA9puXcFSHV4xfPPnDlo3zeO012HdTf28QWOT7hwnw5aTBDKyqFoUNoJ/wONtzYpkMoiuYAWJT8iamrCBSJP5yuQfnQHHBxlx8DVqN2sB598XkvvBRJTjL8c4qU/9sKQK+SeX8OiJ6b7oIr6tnBuHGtmHy/atJqCTlABv083gLA1Yvsl7oI+3XZkqWcuT1JmsJzM5IhfrrcgY66qVAfQtBwd7SLB/sX9286n62FCLu+sy+otl4Tc4l/Cbm9Eslp+LV+XD/XvixqSnrH1tVE6WZv+t0LM6GA6UALm9C0kP+WFqu91/q1hK5WYXTFpeOCy8t1u+ORQUABQf5HtJMxgsk9kDyKO8IlQlLs63szsa3kERJbQyy3UEKJ80Uywhkgz6MDF3U1QbJEfGxgq7wInl4nGFLe9Dso61ULzUHOZD+03JcwS1cVpHXJO+2FsLs+diiV16XqNxUu26YFu/cbCc2/0mbrJoFF7KnrW2e8s2nzdBsWh0qj0A1mNWmhEC7HkmRaqkajhGR5Mqgp94P4/Aoz/BIJoiCXOk+LF8GpodrghGSVDsJBGD6/2om385LE218tCnmShzI8IBT8oRh7IXnTKmZX5Pi1dxD3GG6wjtFL5ecxNRlVPZITCEbj2sIN+HaweqP8rzgufrO+Qbn6uySR5zjAsvTZt1QRsn7m9nLFHy5UzZbrpIxt4x00x14oUpTBnDhakm5OYRkc7Sk4o40mO1ibSMdXBCRMiAs9Wwlw6DfeBxF2zbbNGX3T6A8TsNuZLhQIPhol0KKNNhrIeozqi7w2gmJjTlJQVttg0fin8KFI9cwKlhcuFQknO3bzB5v7RxZEg6PdWQvS3L3f9uuISwgFS8sV8IloXIlmJwqDxE/g8bhRysdj1XHLxvSxd4VNex323H7Uw1h+6U2Pqufa4JDbcOUl8a58DJE69xNs+9a0vKhI6sghAe8fq682BNM0kiV5etoT4AHwr61VVnT43okoUFWBfYRwnWhwn0kDZ2N/Tc6zIF26FRXqKRS/WKNI4KBN51tVRCX73Y9BReSEMAHwz64A4wnvn32orT2ICejTkauSawFGOcBB80kws3zMCcHkcueu+5aZL4687N9h2UpAu1S+JEwE1HGRTb1PP9SHE+/dr3+yVig==,iv:bepSFYamCtmRhGMhoMUmEvKcwGX8KG6bCPlU0fE6dsU=,tag:9by0PjYZ9GZIpja/XPzHYg==,type:str] +MAILCHIMP_API_KEY=ENC[AES256_GCM,data:OSemiKy5JLCj8q3cB6MqPbDe+BJv46zR3Pjh59l4qTTt4/9W,iv:FQ1aBZDU26e9pVwiHjoxit4h88EWYSFF0YS3/jE6edY=,tag:Eg9BksR/m7a6UCDFxnSfCg==,type:str] +MAILGUN_API_KEY=ENC[AES256_GCM,data:vmeClID/tuJeMKhAEBqGuUlKVmoRZk4XH80faSFBSkfoJqX3,iv:ueEV3T3tD1DEgti4MXQLgEohKzgqMwHKK5jCMt+xlJg=,tag:o8++Leq8zn0yESoS2w+GzQ==,type:str] +NODE_ENV=ENC[AES256_GCM,data:5sqC5U2w3gs9bA==,iv:fmfMUpFPGfIt0X6ZAbm4j4oH2pCiRYy7OG66eb7C7EU=,tag:E9fgASNvznjqbzjKMn3VWw==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:D2H1silMP93AZI/N+rIR2iSkE9E=,iv:lmMAB7dcHhFyS/nxjCBtBJrrH3NIrmg+GiO/ba7NmjI=,tag:QQ2KHknsdIisnhgKDrL4Xw==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:l+WkrTk4NsH/Rfc=,iv:fkdyvBn//ULtid63U/CKTXgpx70bk46QMQ4sOBZzdnw=,tag:eOkieRCMvwymWm+uRnpTaw==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:yOdkAwO+XStgzkwo8Nt5RSDU96JexzeU2UwMpORgzNLQXm4=,iv:kw2k3EedL5bjVbCL6CEHY/F0aSNg0lHAKUaCb3EzGyc=,tag:sKwXq+SD3+4O/a4rs3PgIw==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:/kFd13/ExfcwFX4/zcQSQ0j5E/9M0avGQluhl6I00lXS+eJz/qVg8A==,iv:kyJaxXyk9cAXFNeATBjMqC0yEvI3mGwD49a+/q9tUPY=,tag:pYuabKR84BpiYYPyGhoc8w==,type:str] +SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:kXbKKC5rK0JNyp4YXOk1IObQEsDjdbg7lt5l/lKQzfxHi982e25Ux5kTY59xp2SnDyNNVQZaFa5qhQGKG5fQBKDL6NNir81f9hdIimLaPr0f/Z7zw8Lx2uZoOXwf0gSRIp3E8tnNj0aj009miOp9zfd/gHUeSwPXdF2bKxu6SMbSOXoK7G89w8gTLtuKdcoptqIGYAWlUYOKmYiDY7XewRp0vil1skaom6Y+6k+t3rbr3L0a9Q+Cc3pxDQ==,iv:ZiX210v2BthDmlh0wXLydYbj0GzBY/fb7VtahLt46+k=,tag:+2DVZo9oxBFojHtEbFXUIg==,type:str] +SENTRY_ORG=ENC[AES256_GCM,data:9rhL,iv:qWrrpZLPsBNiV85XtjY0MCj4nY3jlptAFmjxiWAXQeA=,tag:7gh4OpcrMGWeRyeLsZ8ztw==,type:str] +SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:azQ=,iv:aqInEL2yeXtDwZPL4oovJhvvKDS1VohqCfKxJhijWcg=,tag:u2xR+O3Gh4Sl+/SBqJzwUQ==,type:str] +SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:XzGfApMoNtWvpoUef/NlC4P6ms9FMCRnMHNs95ZBk89ht1sDOlBCdtRmt2/Kzv4SQA4oTDRA4J357AGZKP8L/UeWswBHB1QeFnz9GvZE3w==,iv:XNrtFa0gGgGcFcyMBxKDF/8tJpRSdJ5Zqi+l3m6M8TQ=,tag:zJrK07YBxmc0X4bfAUoDhQ==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:GJLUyrkiEl4zPyTj6ZxNCKPZaA1aIhMOlR4vJ7fohSrYMw==,iv:aRD1It5hgjVSIMEbtXOJs8/pUaXHsQ063Zfko7Z6+C8=,tag:jl1t5KiG3ftueSdHyQzkwA==,type:str] +SMTP_PASS=ENC[AES256_GCM,data:+pnmEDvbXpNLpnroMv5FD2Y4otwjTk3k2JX82+ve2xIs9dzKMDkVPXkXcqo=,iv:cF/29JYKF+IwGRAWZzLuDt0+ZxJ3K/eegMr8XHG4GDY=,tag:/932F/VShBTzjBR03TPMbw==,type:str] +SMTP_USER=ENC[AES256_GCM,data:FyH5lsmBv5KleZlUM060W11YqeM=,iv:3xr6bPbG4pIW6Jc/pnuP2RAtl94JBYUJ2QdwOsE0mnM=,tag:zOkkt4HVcKjiVQNfp2IqyA==,type:str] +ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:TbcRcUKMSbnh4uZa6eMMzwTGVbc=,iv:zP1yYWFNh2W4d7+IP8v9ut+qTmgZ2LNNa7YzALXKOzE=,tag:LRKOtZYEvuelCYU7m6/kXQ==,type:str] +ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:AuqTkMUytxv42YlsARXnGr5lkXE=,iv:xZdJ26k0WSfwGdkxsLLrpy8vYzYQRPM3edhksMSGOUo=,tag:v8YJYUqGIinUGRr4ITA5cQ==,type:str] +#ENC[AES256_GCM,data:5UNnOoaCSXve3FQuWEeMQTU/bvg=,iv:TvRWMmfiSIgZu1E/H271RZg7t5ZXZuS00ZWUZNZuHU8=,tag:Wy+4V2xZWRQ/p8YRR9fBUw==,type:comment] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:KD3dcG0IBxI65gS9zn0zU2OyqH7XDLaUjF97NBtg65XQSP9LmA==,iv:Rgr2YOSprfARpIclEz1vnvv9d5n02ICtXILisV98qSc=,tag:jIh/cOn4gxP+u/9GsGLgDQ==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:d6wnR+55BHfe,iv:y2qsuRl8+74N6CreatAlEnmezfTRPvOuRdaLF2H3Vy8=,tag:wpsl8Oo9oPJD0KkerDwsCA==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:h7rW7LyWb92196d7KvJ7gQLMJAnTwNw9qV1mmnyU2qwUXottErv463JD/87Roc2NT/VPVrs5lNcTGEMF1uikaQ==,iv:Q4/I1SPV41YqYoH2LSpcThgb89jcj08oebatpmKzOBU=,tag:ef8yNKjrJCpgyklDhY2h2g==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:oDMLpeLW5AjUPjzQmwb59NA6ULq8YdLraJLeuKR5WK6yBTZt4OKImnqQnmYXCN59O5YCrqmNFVJDci18N9BQYQ==,iv:dGYxwjgQn5iomGFldVMWnJpfxLtyQkwazDO+fBVK2X0=,tag:OZjvP9v87af9Ubphi1s2WQ==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:7Fj4JuBQ130186MMlgURKoCGijYwDASqAjISd+hnoQBajNuxXuLOBg==,iv:0O50QSvdldSESMoBNZyagGz8QYUkip7QqiZkcwuAku8=,tag:WZSTC+XX85vPKmWD4i5jSg==,type:str] +APP_URL=ENC[AES256_GCM,data:/x8aPCCcsslp6gD7MwA5IC9WO8tAHg==,iv:MIBAmZCLD21QI4CsDWkyTIuSfsY4BhlFRRQ+lI/7/Zo=,tag:UNR22KrCQdSpMsWWPzXI+Q==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjSmZoSHREN3NVMXdFNzRv\nRmllOGNYUExHRGFXamR3dE9yb3ZJYUsyaWo4CmtQYjBmL0ZpSkdtL0ZKUS9ya1gz\nN21keVZTeTNFbnFzbXhZM2N6Z2xXTk0KLS0tIHV4bklFRWdvMVBIdkpyd1VxYnpL\nNVB0SUZ3aTJvYnlzRUpIYURLRlM1aWcK6Vpqr6F+Fa2jO9ndX5OeXoe8Mgg9KBPa\nt196Mw3nG5v+/9m4iH24/Aip7rR2USnuQ2NWZ18NqdBgb2cstLOWaQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpZ0dZYXlESGZJOXB1MlZN\nQnFNaktpSFdKRFlzWDVQZE8vL1ljRnkxR0E0CmdRREtjNEJsQ2xBWmhSMGJKYjB6\naFFoYkZ6RzhLcW9tWFBnQ1VJbEJ6Z1kKLS0tIDJaN05hWFQ5U2xyL1FCZnduVWVQ\nTWlLZ0ZLQ3FqeU5JUWRxT2NXT0hYYm8KaIgXaWz/dGqhCJwIHbwTeoSxs2ansOhr\n/wiwQlXmoM+0EdsMabjOQpdIN6uEtwCwLBnEbKocMPLJ+gPzYvKtIQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnaXZUcHR2eWJFZWpTdGYr\nUVo1RG9tNzlaU0s1RVNwbGhtQnBocWpmQ1dJCms0NWpGa0dBZVN3QTFOSGg2SDVT\nN3VoNmtuMzU2bktBSlg1R3BVMlJTaDQKLS0tIEdDSTFOdzFPaG1HdW54R3hsdTBF\ndzNtd0IwRlF2YlkwdXZlSnQ5ZFQzRFkK7eC7apakezOG8qrZGIy6JgY2onMw2oGO\nB0Q++/YzEfDOt7sipS6/IgWynq2LjmQweJcsRo+P4x8LiKhxmuKkiQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKay9XQ1MxOHpOV3VOT3Bt\nOGE3ZCtoL1pBRXNlWDIzNkFpeTJNV0NyVkRrCkgzSk50RC9RUWh5cWxiWVQ4WjZW\na2c2TXMvSmZia29IekRpeGNab3VOWWsKLS0tIDgyZmpXUENFd1orMGVxRUQ1MWoy\nTzd6U0J0MmtMK20wdFZhTFlHOXBpQ0UKdUiBRCW7ZXwTbHEkEoggSQUllJpHJHN5\nOVW4Q4chJ5PZHif7llPuzA9WfSKbLTwigwJMgx5QEkuHoFdLYsMudg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHbDFXUGRMWFdJVkZpSm1y\nOE00U1FRbEtCVnhWbXN6N3FQVkxvNWF5NWdNCkdaZ2E3d1VkamFMQjRHUzhGTGJB\nTjQva1VpdUdBbi9mRWdHN3pxYyt6bE0KLS0tIEdFVFJodC9tWWNJOW45WGdDeXFv\nS3h5cHlnNVlNU25oSUlOTlBsZFlSUk0KbztjhSLmznefAXgvroXIj9g5SaNXdjnH\nS8wrDSFixtT0o6ZOC+R0qg3Ny9txbztq8cvY3DsqrQjw5xca4AhEXA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwVzZWUUVrVVBUOEUraEgx\nTTBmcFYxRWUxU2JacUZJTWZTZUd5aHo1amdZCmhDVVdPeHlETDRSMG5rd1FoTmZX\nbjdQYmRpMW9MWEE1UE8rSENXUjFKM1EKLS0tIHV3bXZBQWExblpENE8relRzb0JF\nYzl1TFZIVnhSZ3dBTktTdGlBeXh6YjgKI5XX2g3fnYUsTlE+nsEMoKF2JDZhNcBS\na6ignKJZ44q0KPPU0gZO2ue1S8XhQkM8hQ9zcWQU+m311qa3lKDPYA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2cWlKczAxbXJYQ29FNVlO\ncDd2bEY1Sm1DbnZrOWl2cm82c0FWYitEOEZNCmVlRjFUdGRIV05mVkNIUlo2S0t3\ncFFsRXp1Wk1KaThNSTc5Qmk1akViMTgKLS0tIGxyU0pDd2NrVE1neUlnSE9KcVZT\nWEp1SFNQVjlRR3IxTXJzU2wyZUNaOWsKlBCNRxI/V6f8p6SUkh/FnzvZw0WSeRa0\nuSOH65C34wBw7F/fWqRDSnjrbNp4iGH1GPNUh7ogS4ZkodY2VQqohA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHL29qZU9SRzZiU2dadnFw\nTk01RzMvSGJjNUVmWjZpWG1PSTN1UExOemtnCnYwR2hUT1JOb0FQRnFGMG5sYVdF\nWGgwNjZzdjAyVkJhU2l3YmRMcHpySncKLS0tIFBTVkdCdkduaG1IYWZyQW9ldG4y\nem9lUHlKRWZsTm9UQUFyWE1NKzVuVG8KCYKl5pmdth3jC50UBQP3CVOljXvRVVCX\nf5UDIP6ytEl9DEG3pe8mSpLPxlNw29fAMzU434b53SUW6q1+y1Yzgw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKTWxPaFJkTi9EZkVsdXov\nZC9WSVJPejhaNGg2RU10eHZ2dXBscWxQNXd3CnZxYitoeXhkVXhmOEJDSG90Y2dM\nbFBmNlJJMlJ5dm0ySXpyTjZvMGFqTWMKLS0tIGhWQkY0Q2RwR3lpRFJWYzNid2N0\nbWNYK2Nlc0E4ZklORGI0eHpUU2xSeU0KnbN1ppQhOJPVVjJ3p5Rk0jemcRQq+RgM\nBKJLfupw9yz2fsYIOn2bRU84JMGswDZd9KD/9hOKLQrES7V0UxlDZw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQRTRKdGkyR3AvRDYrdzlj\nMDBxMUpSRHk3OFplTDMwbXlPTXdlWnlJM2o0CkxLUXRPUDFRWE1HR0FUb3VIOURw\nNlFhS1Z5TWdUUzhKSm45RXJUNmJrRGsKLS0tIFFjbkdLTDZBaG9jRS9qVzByeW4z\nYTJDdTMxYVo4OUt6UnBUMDdyZUtCM1EK9RVBqUv5IhF+zvkYbS+Sv/ylYiQ/2Fyj\n+DP/hJtziGHCSo7rhosNwl2Cg+QXJnNMlw4EEKodlOcyEDHZUd/MuA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWNmhYYmVnay9MNHJlOFlR\nQU1KU1UzeGpobCs0RkZEcERJM0R2eHVzVEVjCmhTeDNPUmN0VVhnNWNNN2pLMXpT\neGZFdzFHSmY2c1FRVmdtbXY3Z2FkbWcKLS0tIFRweFVad09KNDd5QjJPbkEvdjBo\nRDAxTWQyWkNVb2hoRGFTRWRaZzF1SkUKYICTvgSDU5Zvh5Xwkj6PYYO4+l4NXd4M\nn3FMQ/HnpwPQB5YTDCNGW64RPgg7DizKQYhwJfvouN8jppNgLS1S3w==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_lastmodified=2026-05-17T03:23:35Z -sops_mac=ENC[AES256_GCM,data:aVDfMyoI4MZSPrGsYxPoFPuoAZRzrKo+aISv9EMPFjSb2dCp2sZbNi6beeMnd2UjLLHiivbFHRH9FgB4arzppTALJ+zTxA7oCzVejAte9up+o52/DQQ1eQyywaB1geyjxr58EBuHAEthz5FMm+X6Cf9Oa6iE6x84aRZ9Per1kfw=,iv:sK6WhmyvZmrmzwKrdwsTaDMqK5yTIChP8V46ra7OfMA=,tag:bxEM8WoaZSegeNSxKjVaBQ==,type:str] +sops_lastmodified=2026-05-20T17:17:38Z +sops_mac=ENC[AES256_GCM,data:E57DxTP+vuC9BlaVev4qkxHeHmcFLjTMjuUzvI1UjVdBSQW8ySig/ys2kRbol9I8yOri9IbHlD+Ii2XUIN7z6KhLVIBHoN6462fJU6uJOs8ic72b1lk2XRBFImhzhuvydx7L/MBfXt6+V/1G/ACq+3g0YuTVkMu2oFiR34rwcK4=,iv:APIaikdSLA5HBJPE5DZDQpbdgD26SX5gdUpIRqM0Tlc=,tag:SQR1QHHm3tU06vi4cBaY4Q==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/infra/.env.enc b/infra/.env.enc index da0ec5e11..c56371cfd 100644 --- a/infra/.env.enc +++ b/infra/.env.enc @@ -1,54 +1,61 @@ -AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:DtnOR9FslQd7A3/tQ4uc5/bZyreygrvCvmh8FFr4BnuAMni2c+xvDJWoGMiOrJXfM8SJXiGXwHmTWnpK4wWYFg==,iv:3mByOY1GW19guprXzyWhlQXupfMeV4aG6ZcnOjnjPDQ=,tag:5vKZpj/lvNuUYzADD6SxPA==,type:str] -ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:FB8caHEBJ68RH+XLuknJB8xAs7NAjoo3m3vLVmthJW4ohY/MOPkWGUJ3ZwE4ye8+MOIj3+FyQpI3XLI9ff0PS3fccHO+jVqRLEUra5eVrwF82xA3Avb6FQ5Fip1tsMvWA0R/iZAMfLz91q+HyB1f1vaIhDfl40OD2tCjMpAHsLo=,iv:FJLTcsH3OZVCTxhrHqXbmaX1GtxfgQHE5wBNToLKCkU=,tag:P1JccTUqSnndn4AMalThNA==,type:str] -AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:5DgOmZNNIoyIRtkIIUYNFO40/ig=,iv:r/JKLOwcirzGDg4GRFzSEqR6Eh8XCMUaMKuNiGOMr0o=,tag:P/65wVGehJnhlw/1g1GKQw==,type:str] -AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:8/ugchv+eUBwP1W4/fcDSup1bbM=,iv:wArSDC7pDcT47iXm2UFP/mIDNm/j3aA8ez6ehVLWrTE=,tag:+CNMCN1BeiDA9SEtQ/URbQ==,type:str] -AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:s9I+3HkZHk70A2DAVe9L6IP4wat8vcTeYkrJIbCvUTRpXXmAwyp/vg==,iv:G6nr/qcSDNiDPVUNVcKTaEHbZ4Thauia7QLC2qvwtq4=,tag:GZW7NMJbjbPEM/lk2DFweQ==,type:str] -AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:WgARPvtgbrOXn4uTXirt3tvKHdviODYPqAuFsdhNukIjtmtIczuAyA==,iv:FdzgdfVbQLvyp2esIVFbQVGuDO2McUMtldkTtgOWn4w=,tag:26YH4loI7deTZjzp/FVNqg==,type:str] -BACKUPS_SECRET=ENC[AES256_GCM,data:WhcQu4Cq8vjwo48kyeHf0RtHWuAJz3NsAG+NqAABb+UssEjxehHC6gkNqs4=,iv:SBGFAnR8fJmVZKCQItftj2/YjrW69xMbCJhsx6PQbSk=,tag:I8q/W9PCWMbqh1h62kNyqw==,type:str] -BLOCKLIST_IP_ADDRESSES=ENC[AES256_GCM,data:zJaScOpFk0Phag2OCBuwgWQir3qE,iv:2ZuGe4Xp+3RsNyZwUpCa7TqQkT8MLP3kRd3WVjc9m6g=,tag:aUIY+usy7StJhmP9mLuGGA==,type:str] -CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:7rkvVhpGE3J/+N14kyE0O/W/N3puG/ev5fBY0+i1JVPV0ye14beK14kBf/7MbkPN7pdidXs=,iv:rYEcc2loeZjL73BBJ3NTd6JYcsDdvakF4RNX9IjE5a8=,tag:ZhgfUXHXJ+1adfMNjWsdig==,type:str] -CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:+tPheMpR6NEbXOyniti787QD8YfCdgcootm10KK5by9FWfJbIqu3ZU9cHY62YyiXk2GHdJ4=,iv:mhV0zJcJRfWSRDleiq845W5SefdKyme9eSYIOBFRfO8=,tag:3Mf0f/ucG6brqC6TlGNBYw==,type:str] -CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:PCwdYZmy9QBOufSq5MgGPJLMTpdB05PUp+vt7VvcGGM=,iv:60KmrslEOnPuZ6UQB+RHiWUu8GW2B2lY7EJ5Fim7Jew=,tag:UXPANQeMulE8U10NQrp1zw==,type:str] -CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:tGRkx84G7TMc6ma+twDJcV7cy8sXnWvPR9aURBsb+JQ=,tag:CTTS/6DO+KiEkGaWhmYDsw==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:Vrt+du4j1aUaPtM23+wzJi3m5H5TOYsuTW5WBcB+/UyGywoMnK83rpgH2Tk=,iv:O6HtqO5wdgds+fkorxukESc05bs99L1JJrpEVW/g8l8=,tag:X8OnCdlGG/Lt4JTwH5+1yw==,type:str] -DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:8JocdzaYksxJpCbT/jZcSpEhk4Bt+OKQFgyYx0I=,iv:RccjvInQf4RyWjKbgeNjXrdn13ZkajosW48DKd8BTek=,tag:uhA8lt852nPKqGmtwGDIyw==,type:str] -DOI_LOGIN_ID=ENC[AES256_GCM,data:Su2Lh2Fk,iv:JtVU5RxcRuyxxfAE1H7xMby0IBwGWfBap1OTWduvrms=,tag:yvUfepFL7Rzg+zISDFBPwg==,type:str] -DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:uOwpgd0WtlMLApZHV7BKz62H6GA=,iv:E581+Gq/x/0NW2u6qznPNSXgM7cHdJxTbWtJQ+Ge6pc=,tag:BYvYA24ScYJ0Coxl74oDuA==,type:str] -DOI_SUBMISSION_URL=ENC[AES256_GCM,data:cbaJvId/YXeI+HoERODrBQNY9bVO7A5GF5A63X1skuCcBJdJk6atEA==,iv:J2+VZQmU0Lb1Ft5AHHQIyX0dYmHrC7Ix2H6Qjq/ZnnI=,tag:IK6TG3FSNRZDFicrG3fBVA==,type:str] -FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:YESGL6a3zJDvogpCPGX3V3nSLVAHmF4hDOwh8q8pryY=,iv:UnJ/l8+/mCQl3a+SudqLatl6QhanANZeW9FG8S8YEog=,tag:ysiYgJu4BA8mmrWOmIJ9+Q==,type:str] -FASTLY_SERVICE_ID=ENC[AES256_GCM,data:LTeTy86QFKpjks8KfLOup7SyAGWgBA==,iv:NVqPYIKAVmH+gb+87O7BeOrso7IzB1INm9hpoMxaNUY=,tag:JIFqm6/mFajwPV90OypBBA==,type:str] -FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:EaGxt4jGDMmdpqnvegtbzaR4IJXVNsKX4Xy31MTKw0o=,tag:rpP4/i89TvHpYV6JaSFP1g==,type:str] -JWT_SIGNING_SECRET=ENC[AES256_GCM,data:72wuDCGgAvZJ9NUq8p2IoS0n95dWWB3NCdr0osTN0p4hv9SKy+NTw33tjpnKKfTm1aGxvmKGwWrbfG5o0YDcwRq2+kHRSjECkZbn9GqilRdE/D+D7ajhKTXkei46AzvvQnf7cx6nYC1JKARJJyhzL1avnw/4H+NwnjWd6ItfDpAbyLQwuCE8Oi1VwJnVSfsR7nX2mjxLGTVbj3iuagO2aYWCm+njQ2vqy/mHHOjT4K87AUVc5IuSbVoV6vijYNXB3W5WOJzKmWRPVeIBnxNqynhkzapv1Br3UweZFXdQXBgy/uCxPi0Q6J8jNX5G43M5uy05g9bloqjm2hLmpv4/2MQTin4TjxnxWgtRnUFMRbKSrgzbKQ+sFj3gKQ+GGYU2uXS72M4Y4uN2FhaDAa+5ueUajFm/O5D2IJ8UFtGjGkzJrIlNW6YS+aal54dAIgUbrt9T9T5Xq6w1BSYdoT7a0NHqebb8NymF+scZhzLNutkjTQEabe0Fy8tKqCwmCsli7VPq4iPpLFbw5qJTDfTMzHsDen/+qvvasED3nyI7T7zM6gmM82XwmLJlc6V6mfxwbvVF1XxZRyZTOt313Y+vst+esn8tNwQ4hW1FRzILZKLjc3tZU60bvdAQwoxmCoJr0LUVVU+c85lMFAaDH+GTUfUfDkP52UWJ1wLAoLdKNAw=,iv:VxBtQST4kqbwwq9f0zuPtE4qqbDmk3a3+Wwy1cRAdCY=,tag:OJjcpbN6cpyP3VtU01usAQ==,type:str] -MAILCHIMP_API_KEY=ENC[AES256_GCM,data:St9p+GYB2JZqsbqpvFWsXqiS5F3UoKU30iX0ibhUntaotKJi,iv:oaooK5kVhd7MDtBOMkwUlG9Wwdrfq3HGUDDkNeOYXbM=,tag:xVkwLzI9H2ExRNo1gG6nsw==,type:str] -NEW_ACCOUNT_LINK_COMMENT_WINDOW_MINUTES=ENC[AES256_GCM,data:hS0=,iv:FNEBLfxNbUZQrSxyjKh6k1NLfOX6Lm4GG7+WFFAO4kU=,tag:u7QT3mZorvBmi8UXY+OQ8Q==,type:str] -NODE_ENV=ENC[AES256_GCM,data:jzwrtdbmFoGvBA==,iv:RYD2R56sdOxsACNhO8++X0423xEwSThqVOwaZ2YaWJ4=,tag:msVdFqbaTcp2QGqahAo8Dw==,type:str] -PUBPUB_PRODUCTION=ENC[AES256_GCM,data:zwYrwg==,iv:dbVTCcunNCDAUXHBKmfJwOet5n9epF2FCfNnTABQQBw=,tag:C5paBnweU6l+4Bwe3iOirg==,type:str] -S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:/YiCUr7V0+qyfFTPiZpaxRKbKyY=,iv:jaPF/enQZ55tKsnel8wrpUmUQiKe+Z9LOzIkNxHnUQI=,tag:m2pn7+NzHw41DVPvlGwcow==,type:str] -S3_BACKUP_BUCKET=ENC[AES256_GCM,data:uVJrmRqJ2z8VCEM=,iv:t2X/7DOumesRdQhxoTZBc1Za0zq6+F3oreYKEpBQPAs=,tag:5egKbV7idgAwNpZ35HkcMQ==,type:str] -S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:y0LEJqDVugX9/tmL0XTfyvFSeVV0cSJ2Pg3mnscUPRGafdg=,iv:ot0c3oac4lWf3gCOBbxMW71OJys9DCnH0D3efNNsluA=,tag:yZIsie890ijP5m/oORU5Iw==,type:str] -S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:HuAFX/fUCahjpSHzR6IzBQE1oJcX33go8iZMRX36QsQIfq5j7IHz3Q==,iv:bsUzuNX2L4pl4EiWG7Wl7+f8l/zHPflvKcQO2AtggV8=,tag:PAKwAxX4wiFWsaG92cx4Xw==,type:str] -SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:CdWM7W9oNKxmncyYtYgj3zpKZp/oB5z2SJ+XUWvHarY1vrX1vc0t523p9aqAfIzgqyZp5lrmifHV4Yhhhc/gLydoNrLF3vHjr5nIDFY9JANq0fJ6gUtb4aGCKIty/Sga57o8bpcjbPdM6CZNf50UH219eYU5XyPXuuiGgFQYPGYAOa1rbAjvYsorUAD49uIcW5VGDjtXh8k1jRS3nf0X2yWHVM0eGUemFfO3Y//JCpEpjSy+Fl7L/K1LvQ==,iv:aOHFgmBCFTw5NODpPBhs0CePTrlImvDJoitQdKO0/w8=,tag:x9YiGhxTnKSAX7015tXalg==,type:str] -SENTRY_ORG=ENC[AES256_GCM,data:xkvy,iv:se+o068ibuiqYQNxMbfX7DclUH4MfNNYOARVbPT3WuI=,tag:Lcqnk7QLtW+4bu03qD7ukg==,type:str] -SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:4cc=,iv:S2baOXfar5SmWjhpRPTltK2i4AKqqSUlyUtgFXknlkg=,tag:fL2wFoUarK2VRisolgAgSg==,type:str] -SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:dUi645vseIRPgegbfUfrzNIbFeLEGgZBfEV6uw1OY1uw079g7xZBJgdQnMcDvka8lR0jjMi3tphCg37ec+5uGJbcH6KOq6EVz5qZn88ElA==,iv:YIHNnDbZKdFCuLtEeX0/y8W8zC3FBJeP21gjIbt0nL4=,tag:o+T/3JleZFR6cukFo0CtuA==,type:str] -SMTP_HOST=ENC[AES256_GCM,data:J9FFCJd95Fvf4xAvHzBnlPGIXyobkjlkBveM4a1q+ZYYAQ==,iv:LLSfinAh4ADrxWIkW2daOkfsZ/GfE+p9QOVAnnNVsBw=,tag:9uMbu+eM+8Gujwxjst5Xiw==,type:str] -SMTP_PASS=ENC[AES256_GCM,data:/Hd40twt30Iy1tlb1ErKjaZzHYz5L9ntwUPW39EHNEbjN5NUBkBCdRXU2RU=,iv:L9VrPDHh6+LiTt62ue1rD13g5Y80NlbBM4h+SpiRsv0=,tag:G3XMaLf7rXtGLQMZ12UXzA==,type:str] -SMTP_USER=ENC[AES256_GCM,data:/Y3R6n6CMYyLoChd0izt1QtO+38=,iv:4rqRcCCUrTWUCmu7PzLOPHRl0xnpbmBNxQC9rUgbCwk=,tag:pBkePy9loHDK6pS5cRI1zA==,type:str] -ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:kK3Bw1Oge9rvLkduxjFw+EVNQAU=,iv:LX8THgcf/k7ohcfGLOQ5n+y+kPn2KdmXyqjtt6Z/moM=,tag:xXtAxiZBJnxWMx/WKentrQ==,type:str] -ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:4G7g0Pehiur5Qr9jKJNCaBVYzGQ=,iv:8blu6Qbv6QBN2KeNc2j0In4PMU5rhlxWe8aOZWkyiaE=,tag:VKGf8qbTFIOfoc9g8QB6OQ==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4aXdwVk1pZXNwWFJWamdi\nekpjazVUZ2tpY1VwVHM0cGU0NHhVNXByeER3CmZaamVYQWI4UWtYcVBXMnFIQWpi\neS9keVA4OG5rS1FibHJ3Q2ZIQldJYTAKLS0tIDBEclR3dUdpQnF3NHJndi9XRUhW\nb0dvRUlURnZaMmVXRDRDc3ZrSkNkR2sKNaEr5SQdjKj7tA5hnI5LlxjKxBXLfHJ+\nnHSg1I2kP4gQ/fthum9YGY9pDWy4FGZ6sTJ3dBaoYj6SY1xVdphIxw==\n-----END AGE ENCRYPTED FILE-----\n +AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:Rb8p/Q7y8T8bj+QsSUoREgvQQ4juEJkkQJOZ4HCEG5cETtMnX2PeKEmGJnvCLDGQxcmcTNY8FOfpoQNQn+c+BQ==,iv:RYVAAggyGs7E3NCFFeYpCMdvv2iGT7Fk+vFUYxkhwTg=,tag:hvs14F7Y8dnBIWf3+Hp/Rw==,type:str] +ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:HHd9xcbgKNhtIFHRXNakSn6eszeI3hfPLfjdgX8CUbP283Q8g/W8naU3FMG43j6CxuAoZXoimo9ua9l2ClqEaL89oYp8UkPf9ySphTDIrwzWIYelEmFDUxv2kxaTIwnyziBSXBsM9Za93IxdU3WBiIBPU/xl89tBywzPTXVVmqk=,iv:0Iv8rAc4NGxYWgQeeIcvUtaIC/e2EigMLIlIpCHw/wU=,tag:+9Ufxrh7cHIHuBZxY8T/EA==,type:str] +AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:afGBZUwTv7JPJxrU8O6IZ0Ff8MA=,iv:D67ERuD+In+ViOquixuLNomlAzxtvFXD3Kg8z7b0Dfo=,tag:imReGZGqRoJC1+8FwSP87Q==,type:str] +AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:PPIXUkRfD/K5pMg73BH0Lr7v2uA=,iv:IOkV8xlarjgPwZaA7MailwtUeNOqY8L6+Gzzjt4jxHg=,tag:XSiuboy5dImloSvVVU0n3g==,type:str] +AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:R4WFN09++pHl96degrNkldpTdXwkW0CzaHugWlrLg4Tst9jhumc88A==,iv:Hp4lbrJ8qjf66FL255nmoYeKKRnV56Oeuwrs4GUrkqQ=,tag:IIJTWj4v8/w6cWKh9lmjzA==,type:str] +AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:SxZyLHf3uqevdyvYbqwzCv3MR9lQ2E41dkcIFuJ7bQvHvj7BvPu7eA==,iv:Pb52mdAjkXdc3Yv2oAqYHMgAoXJmhZtwb9dsUAtm/xQ=,tag:FNuwxcuGBSOKbyXoc4tJUw==,type:str] +BACKUPS_SECRET=ENC[AES256_GCM,data:BQuPEsZJ4nDaPBx6zI1SyE3zI24NqdUj2vOWJJA/83x1osWZ/uIA/CA0E3Q=,iv:isGQZGhJ3qrZzZ/A/3uxDfu3P6OZspJFuh6wsmzZGGo=,tag:dj1S4E6RpXdvo+iKKmJneg==,type:str] +BLOCKLIST_IP_ADDRESSES=ENC[AES256_GCM,data:vOJNtI4XoIt54mUO2G0Gr7Imlf/F,iv:uGte2NDGJ8FDzQr04HEbyy16eL9N3iQzAuEs083woGk=,tag:8QmjriYUgfVv1ea4NGUk4g==,type:str] +CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:6XTa179pf+WJuQezPfS18iTZa/0otUnG+fGAuVFUedloyFbT5DmHxYWLPJ1pDa7NUpLni08=,iv:LIpI+a2iEELKIE3jolG22toVBsouivpMT8Flp1gxhc4=,tag:TZ1TnmN3gT/Sp5Fu2y6LQQ==,type:str] +CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:BT+IafsvZ3vHtB0678VsR1IBsIuaYPsfBKIPwT3xb4wQ8TiTIEE0KRFYSoDsxu11Wg0q0/A=,iv:p3GAMzADfVd7pAffkrP9sgWcKaMUhTePXSeUmE9TSUw=,tag:dwNJIvRS2oTA1LY4z4+V4w==,type:str] +CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:jq/0WRN6Mk1xGwaDRZe9XWiySh+F+ZyjHfDJvg9sxHQ=,iv:ICJiWpiiXTqGv4VVodwXUvaAsQ4KXVGNXEKSTwAUTRU=,tag:TT2pXOQXZh+YKrzfow/Fpw==,type:str] +CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:wJYo1JlO/T1r0V5xV08PWUW/ABKaGWtwliI9v0y9x/4=,tag:6U1yQelpvoBVwczyFGsnaA==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:kpCVMaY3Ha+ybplD9VPvCeRQEt61yoSu0Dn6JR5Dq8PtU1rdA1Ewk0YiCT4=,iv:K5yVw8w7+UsG09xXZTHC4Q0Z0VylRQBVr0h/QuuS7TA=,tag:UnEm3S6LO4/V1NB6mT6biQ==,type:str] +DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:AWe1swwvbchmZR74yfzheNNAeTx0iZhzUS5OoBM=,iv:vm1oWKva8CoTahrJRLEtQpKlcURrScVhQIHOeVSGt7I=,tag:YCmRyYujj1dwBD9KaS6DnQ==,type:str] +DOI_LOGIN_ID=ENC[AES256_GCM,data:ztqYO35m,iv:oXWNxzAPIqaQ9WYrbbi5ESNhT/9s8jakr75wEfP7Aw4=,tag:m8ufweCmoNhbcb0iS5RTkw==,type:str] +DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:uLERMaUTWMrpkiHmRhLW4RT729Q=,iv:THMJshfMlF232SRnYgdz5IcWC7q6h1vB7RpB8+Cnyxk=,tag:DS3eDHZVORVrKE26L7hq3Q==,type:str] +DOI_SUBMISSION_URL=ENC[AES256_GCM,data:oi98KqaBFe3pSAzSH+Rc0GYJR3SA6nicj3wx04J+vx0D/490jPNRfw==,iv:lNFpyU++R77YzGUM9elVCJ+j51G3J8SgOmEBJVtbKX0=,tag:xWZqnH4/b5wYPwoXsfmlXw==,type:str] +FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:1j93DIsNHb9HJ1DTHMCy+JX95cUIj6rJnAcWXsIEiXw=,iv:PfTYmWxl7lSY6JLhHbnFeJKKVMgFuMVzjum8NbWOiw0=,tag:uN4MGUe+KehHFsSVfwsJ0g==,type:str] +FASTLY_SERVICE_ID=ENC[AES256_GCM,data:cnhHu1xfiGQAZpTbdlmo5F3w0bsiAQ==,iv:8FFbwbJnkgR9vh8NU7sKMTaTKMgCpfjlQqgN4enSu7U=,tag:R+I+XANrlDx8bRo0rJU3jA==,type:str] +FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:wDDidSsOKgcynBnC7bxgADxJT4C61/OmGb2XtLFbIOo=,tag:Pizy5E49y96EYTI1VQp1zQ==,type:str] +JWT_SIGNING_SECRET=ENC[AES256_GCM,data:BGlvfkuWh0iHW/Hj96cZSY66xiK+oChbHzBTpXPLbhcwUYkDOjIF/bjwJZeiVfGwSmfen7ERi6ZjasI6vChiJMCVqdmjShInlaunOEUB0f759kdtqtN6jOKp9ZnMcQ5MlAE3N3XVPhNS0VuBY6F4zwcMeGLDXbZXc7HViUjKtJd+8OttLGgmE7MMjJ5+/ajn42JaGYD9Q6Bb7x4ibb4J2BFRO16m4iH0KQXEoWb20ikkh4H4EyC0h6BgQ49G5hnJIukkziASwDt1dN6NNuRU7RQSkPYi20x35tyDx2u/GxHAEZxdL9S5BHQV7PW4OyP7Kw9pOmVSFmrIiHIO3V1Hf2O2+Tm2b1qtxkZagMmXjMOyurtAoKlfXLIpEdXumSWZivFdnkfyXrGp8cvlajY1fTG87Q8n/C78AJ/Z8oiwN05IursUlHbSE2xWmoY8IEoLvphfZSZ2aZoeBUbe0+sjnK8Utkd2vInwhZUaPo+78jmAfHtcscIMzzAdZV5u7qkelkhDWOuo9ORKSbZ+MqcuSSNekUqIiA9UE3dnkYLgVtM/6B08nU9i4HkzXFyHh0AqmOMqdSBjXXSdAMPV+AjadNUGdyE33lTzPQun6VfiUNuOTt1NCD4IjS/fklvdzKuw+xkIZ/VhGEIr+FNxJJUnT6gDHKkAXLVotXYXwBJfiWU=,iv:10/6CnMbjNucMtV686xPWHAkFVzzl/SutwG5qafOtO0=,tag:UHFHQ7swB7kLS9XkknSKKA==,type:str] +MAILCHIMP_API_KEY=ENC[AES256_GCM,data:DsCvDCEZ3fHGc6W4XMFdN1OHLi1zC9+fwAToBOOs7PFO2z2V,iv:8XOGBn/W0OyhbLO2dYre0ukgk9xX40u3M/vLttz3BFY=,tag:7YNavzArdtVh8LoTKKf2ew==,type:str] +NEW_ACCOUNT_LINK_COMMENT_WINDOW_MINUTES=ENC[AES256_GCM,data:V4k=,iv:aqYkd18RYpa7kuuzbpAmSk9Vcr47BW8kIZm9gKEwEXU=,tag:RIsIsobpnql49/0WX7ymVQ==,type:str] +NODE_ENV=ENC[AES256_GCM,data:KwOcuP7/y4p1EA==,iv:T989Sim7c2uyqTH8uL6yeAPajoYJahzFemZcSyI/Sss=,tag:mkChCafe8w2VZy5a52tRhQ==,type:str] +PUBPUB_PRODUCTION=ENC[AES256_GCM,data:XjBuCw==,iv:nSFjSKPbf4Uu6hME/jDQxce3cYH1pJO8DJQMF1NyWKU=,tag:NmH5qyOtM96tX5EDIR1cTg==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:/QG2ZvWm5+yRLQ9jk2WoiAjJMI4=,iv:8zSs4ZHAVpf/ZHkkugQqrpCQeF8aKUCOwEeE/Xoknlk=,tag:JIMyV0NLIZy26dtgrTK66w==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:ahjpBwf2y41SpCM=,iv:fUODBbghyloP8U8PbKACtr55E7cm1UmgC7CbpTHs8l4=,tag:LfgUFA5rGG4zqw9JMb/UTQ==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:ovFYPVj43nPuTmg/UtA1JDtyUxToGLuGFMZEVc/lbEmfeqI=,iv:rQ+oqx7YraJkJMBB39GNaHAQtI9jUAeehv0BdmWBUts=,tag:oyW7YFacdo9br6m27u5Awg==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:3Bj6Gffv8F9xrpfh6WkEXmDIsCMSASObbHR20oP2tUZfw15q4ev1fw==,iv:kDmAFX4YXr9pqtRJoqtPlucXZGl5mWso4FpzuGo+ZhQ=,tag:Jdw9gj6IXNAdolEBSvhT3g==,type:str] +SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:PUaTTaw3pyiUbuCm0WDBoNKwt8rUv2gKKzX0sROPDCLfXxgwaCWTV6nZwjxyaryIpsfpL2x0zfvKH/AjnvLc0AFbBO9sU+cCrq3yuQFCuK2T4olrOitaZGbigHpiEtCPM/laCRS5vxGhbENynWM33mz+jHUEsKNFcQOyKzb5U6SibDS5Cs1WeFoPGObslsfR9FinmMpehf0bvHgneEur0kPw7iHdtpDIJf1/1+o9YcRqJwvaaUx+ZVXYVA==,iv:gd5qwJM6wMUOxwXcDZMPUPXbfSRRr29ZMEZX+SxStFo=,tag:/v6LTnbX71Rf6inniPCCbA==,type:str] +SENTRY_ORG=ENC[AES256_GCM,data:5mlX,iv:HYvoEOkVMOTw5I4/5n25G/4VJGp3cgU7TKYFpvbTsG8=,tag:6a9akdj+cQUhusG2gxi5bA==,type:str] +SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:qSw=,iv:i+u6W2yUFvEZhgJwWoMj6sQTE3InQrm781t7b3f10gI=,tag:hEwRJ6hP+66kskzxsf/sgQ==,type:str] +SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:wTLkeHcl5sRBZwS0cN76m7wzK08jMgaGbENvxqLOLDajwV6QcI1s86CGey3HHpef4iUX22ZKuFZ9Q8FyS4x/XwQi1cckvzjznqF45N6BXQ==,iv:yr8bcynfv7ASsta0iyB0I3ex2Yej+lcUE1XdmGkrD18=,tag:h5NFEQ0tWsfCra7v+wQXcQ==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:BUyE1bVMOJEJOtgHqkHU9hxxMcM4BN6se/ErfQvZjvTYkw==,iv:JaISCEPObi0iArR0dr9dqSg6AxIFJbFGGYRewntPFxo=,tag:Lr2xuIvVz9MIwVXvb01wNw==,type:str] +SMTP_PASS=ENC[AES256_GCM,data:jDTmSuleP6u35yLS+u6R7Pm3jgOM1sKTBajDZ9QWZ5pBUN5LdI/X8Kly3vA=,iv:q7OLmKSfYT1ggmF5vL3QijvwoWBzLbsZ9AsZBXxDWhs=,tag:CtAOweypxrewqTLbxEIowg==,type:str] +SMTP_USER=ENC[AES256_GCM,data:pr660T3ADIaK06QaP3WHaD19nj0=,iv:fNEeFbvdPa+QYyXpGGJNkUCZmBvtuU/j2c0w1HLlTqQ=,tag:u83hapQheqPzh06iELenQg==,type:str] +ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:TnNAGrCWHBPFJn/bdvXBG+dcTkI=,iv:xvQtGH/6zaRL4LgtAnO4zloLBh8SxnHByasRo6PTuec=,tag:8Bg3x/oVwLEroJCY2aA+ww==,type:str] +ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:ujfa/HTCpDoosNoa6TxDF6UQM3E=,iv:EdC4fX9W8XdObL35eNRzcT2UgUNmRfH3NAeLgqytAUI=,tag:GErhJOFLqVhia2G3rdJ0PA==,type:str] +#ENC[AES256_GCM,data:tUzJpG36v1MCZVwtDivOa2dPI9E=,iv:XAaV6kSZUxcAj8W3R8tjiAu+ZIKyFaGiOReyBg1x4A8=,tag:TZQErGhvNehnHw8G76Y0VA==,type:comment] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:7FcCegV2iNhxPWuZcXLt33MpOjxKoGZQTzT3lQhjpEsn,iv:/OQgpmtPttmd3Zm9TFUi0U1xaQOp2Jh0hrjp1g8cHsA=,tag:S2aWuK0BSN4f5wupxd9Mig==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:rRPEdmmq8tyb,iv:K32MKM7A8mj7zJ1rnKzAYTVaAjl5gEB0gtrdMtCSYYU=,tag:pH7S/4tulQjantSnBr4SfQ==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:QaSFOmYu68bG0zAjSstcaZmh8jgH36eRKiu450gCFsJomt5+Z7egA8UNqRTvLf6i7FQ+gJvBDUvqSzEpZNuuXQ==,iv:JsTveBXK/CH/gD6YaL4IQ3vyJGujIXmRH4ydmrvj87Q=,tag:qJVn8dPnmgIEQTPpCMPchw==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:S3S2Gq8xnsvlfKnoaOEaZ7Akpn9cZQtazJGTay0k9Baq7uqZRSy+IWjoB2CiYLr/CAtESwcHweY2FcslltJrmg==,iv:6PGhhLcsORNTJH/xFzbZrAh5mx2UNR7bB85vhOYPEp0=,tag:sc0VggJh77D4GtMyKfmIIw==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:tmhgeJpCj3kX52riHjncNk7nVNBUw1ic9EzSOlyW9HEOr83t,iv:k5+OtNyPHhgBF0XXpaCVS8q5HoJhYw5QeV4q/Fgu0ss=,tag:kyUUke7FJ13UQsJSxG4zrA==,type:str] +APP_URL=ENC[AES256_GCM,data:qNnlTBK+bpluTRPCL3gHGgekA5FtUw==,iv:FMAbRxulpga3W+A/bmSZbrmtaY8Cgmqu4gJMEqSFlps=,tag:DnFx1Y9Rhyho80fTQof3qw==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyOW8xclhZZldKbnN6VGtR\nQjIvUFNrdXBRTTQ5RUtiNklsaUFOWkZwd0ZRCjNnbjFIZnZPeG1oT3c0Tm5CaG1O\nRXJPY1pSS1JET3BqaGprME4zTVhPVFUKLS0tIHcwemZWbFpTdlFuSHBBZFgvSHdV\nMkFFNkI0ZGFKVEZET3h1bW51OXJwRHMK0LpTnZaAak+N3h58yiSJMzY/Xw44iC6t\n5okiCdXSTB/fl8VJAtaNwqRS93dLwdzRAPO94KitKtYdeQlE2s4tHA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEMGM0a2RRUEgyTmR2TUdl\nVzZwSFNVMFJhcTQyNW96alV4bGsybktaRGxRClQ2dS9xcFFRWi9aTEZCYW5PM3lo\nYzJxaElGdnZrZHRLVUlnWmdIbk1KQU0KLS0tIFgyRHVzOVZGaFAyL2theGtyNFlU\nU1lWTmRiM09pMVdSdkdybUhZMGpvU1UK5twbjHrtZwk3QTYv5JfgsAgrcHDytYQ+\nM1yk6QHt0loScJ9Vpatyw/StPCYS7Avpfk9p6LZOIQ5oRzYLAVmZVg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpWFZKYWg0bDJ6QmJMaXp0\nZ2tnbDA1aFFycmh5TDJMV0pzam5HTlR1LzNRCnZ0cEQ4ZGRNMm5NRFVJRWV1NGNo\nc3dOLzBVK1RRbWY5dS8wL240RFphRFUKLS0tIGIxMTV4VGdaOVRhWlBNOUlmbGln\nbFIzdUc0NEF0Ymc3N1RzakZBSDN2ckUKkAdNqWbWaIS5SK1Wd4nMq2eGTttfGd0i\nlgGytU6HINzF9yU7OAeK73Q6WWunHHy9DUYoD3dl8tedbxN7FlDxsA==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzZ2NnSU5IN0NMVXpGdGJ3\nVFYzd1p0alExOGtuM29EVW5zT0Fpendac0FJCkxZaWo1UlhjN1I1bGMxeWFKWWR2\nWUh2YzZVQWUzN3BkY0toeTZaRE5LUlkKLS0tIDdGdjdZdXNtWTlFV0NGRjVvZWo1\nejFRNjFCMm1uY01qbVJpUUVPbk1ZTTQKx2WXbhYsAHWNZQJhzlrMuBqTEoDKBprO\nmRKwWuJ/Eb7gVvXot3xhPAHws6ZFcidf9LUhaUCkPHz94hDZmzP9Tg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2SXJDTUJoS0NqWVZ6cnRB\nYk9YejhWclVwTkxSSGJSelJJeDNrYnB3eUhnCmprYnRlTElpajA3TEFCTlk5YTdQ\nZWdTTkpyUVUyVVZuNnQycmtGZmhaVGMKLS0tIElCeGcxb2NqdEhwSytsWGtPSVU2\nTFJxS0VsWFFBbm5reFBQRkZaOTVneUEKuMJjoNAQtQipFP9Kod0ejlO1SX9PpWB2\ncR7Q3VcIUf7t5u+pnpJ7e56lHHcaAfiOUQFsShvjjXBLwXTeFu3IJg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUemtOQUFudVdzdHJZZHMz\nYXp1R3lBeElVZGdlUHF2QWhSYVVvTlkyYXdJCjlOUU51eXIwWU1aaG9UWFRkWHV0\nZDk2eUtpc0ZyVmEyUzFHZTFnczlIaU0KLS0tIGpwT1VRUWdRdmtpYnJXUU9MUDds\nbW8wZmc4YU1Sc1V2VDlkMGFNNUM5LzQKq0UU5uv9ypGc01/JO+kWGJu7SQNGDuL6\n1ldZXGG6h4hMYSFsA7blvnhcGWdJAlU2rhNN144hQjRNAj3JlewpUA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJUzVqRGJUUllLbFJGSlRC\najM4L2lqclcyVWpYcElVbGlsTm9yaXE0a1hBCmFRcDRXV3Y5RHNvL1VtV1Y4UG5P\nUDdYV08zV1gwS3I3QU9aUlhyV3hITzQKLS0tIEpLZzBPV0V1VVJtRkVjd2lmdjFB\nSjdKWHJQakF1K3ZRZzFNanBkSFNhNkUKTq/DTismWQDjyf+L8fxq2VOiNUCLXAse\nfujiuMVcrYIKXP8yR2tAyylsdDzto/g4e5RzX93/GsMnlvWrPfPZJQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwZ1BzUEVzWTNYT3JZbHZ0\nQ2owYXZJeDk0LzUrbldnUGVWRjVScGZ3NjIwClVuVmRsUTRUMGZJQjlESlRVK3dT\nUjE3d1YxaVB6ZFBMZnk3dE5WNzRRMUkKLS0tIFFKUStlSmdRWVdEWEhCZkZSbFg4\ndGVqUlo0OEFSbmVaRFBQWENIcWdKNmcK898+HdJV0lVa0/Jrf06XKZtHuPHQQwxb\nu6aDPQ/V6N886gpFtJyzB9WaeCMyFKRWRD48tTPWISZVmebnL7XCGw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpRXdaTFRLa2F3Wkg2T25o\na3V2aU4zZ0RRYVFoYjI2OCtTWURNcWtYTEhNCkNIcW9Zcm9GR2JOUTZ4UlBUSXV2\nenJNVExldnFBeEZZbzMrRVZuaWRFQ1UKLS0tIEJUMS9QbkRMazJpRE40bjBJbDlP\nZjk3SkhTQ1J5RFk0WDJKMS9tUENrRVkK1d9PJZF74p9GdFrmSLazCXBjgXRwNZ9G\n0QDbPasLQze8o9s55irb8vTAI+yQsdJnupHR8qDxj0AAsSRyl6Nt9w==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoNHlCUlpRSXA3UWoyK0sv\nalUrK0tpakVxakowTzZ1Y2VHTmZmbFJKS1YwCnB3bmN1ZEQ4ZzVpdXlkUmNWNkdu\nVEwrelhHYkgxZ2xLTm0veUs5Mm1tTEUKLS0tIHpjZElvMCtDVUpFTFA0L1hyeFdH\nWlR1RnFpT05ZV1ZwWEx4ejExRVpCdG8K4KZGgNsB+QmZjK9C7eUJuJlPXHq08v5V\nxD0IyEasHXorMqTUDHvwa18AU8n4BaT+MCTRoYTN2h//G47vMx+ruQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjK0o4TEtERVNlNCsrTG91\nSkg4WktLa0xMc1hUbVBtTS84ZGhZQ3NRWFdFCnlQRmVjdklya3lEamdESjQ4VWpM\neG9kaGpyVVJadjY5cUwwc1NpUi9lSU0KLS0tIFRERTlEM3FQYlM0WjhsZ2dNekhm\nRlBPb2p6MWV0b2dudmNxUVgvcldQNFEKvogefgMFzKyL6VjrbNUWxTMDKl0+CeAR\nbf+3TNzTxurazzgqNRgUheXZ9mv4YKo5Yu1MNgI6Rm0JJwfZryogsQ==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_lastmodified=2026-04-23T17:35:46Z -sops_mac=ENC[AES256_GCM,data:LMDQ4dAjsRljCyGxwY+Za6piTZRBo8+p3/HuVybwkGpm0v84VV4ksWcjJWnQ9Ii0hKzRO72k5byLBEJNWeUC/+Mqz/yUYn5lMDy7I+pJV0fy68qmlkJxJWM1I7pna/0BJWG4INjA8HT5IFyov7vGzbOFhG+3GvlUk5pvLu8N2YA=,iv:jooWNJe6n6qiHUNRn4P3FuhEvuPhUvqVTujggXp13XU=,tag:+Tj00rUBm/UDvcvFcroiVg==,type:str] +sops_lastmodified=2026-05-20T17:17:46Z +sops_mac=ENC[AES256_GCM,data:aysQdMNX85UzCFkaOdAJWm6mV8XSpCA2f86VMGLcuMa0AoCsAf9DClVQh78OI/Ut9mDOPqKr7/UBSUJSVveP5x08vCvXaRTyX+BbSobD1Qfbbx7d2Z66XC7FyEv7dVz+P5LBCSQWBpTGASP9DqWlmen6uTpYJB8oqQc6Bw+84tM=,iv:+RR0Kk8gVnwvtqKh9y/6O92Ymmc2ksvJZnzyFaXaFvQ=,tag:6B2VEwUw/c5cMEaF0TQ4ag==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/infra/.env.local.enc b/infra/.env.local.enc index 61d94c74a..7c15703ed 100644 --- a/infra/.env.local.enc +++ b/infra/.env.local.enc @@ -1,60 +1,61 @@ -AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:jSedz6UDHpTk1faKoJKFEVIIkIhX3KJc1/3l5AEPqPfuNN+5C8+PQqTYUENQQ8Vx9DqIVeK9KkUUSBgB/XEe+A==,iv:czCsVvUb1eD4eZq4k9ZcSbYKbryCLOYNLYQOc6NDpA0=,tag:+M+bdWK8tDXyV2Fd6VRsdg==,type:str] -ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:3hwgS/yhoJlWU4rB9XhZHAS5g1hpx4spbVR8L/b/NkSgAlnSScEIqIiKL2nkSMdkdZoUKbUWXhNOI0GwYkWYqQ==,iv:aqfix2BNDJl9m3QzWYSbSh4qIlFiFCodEnixm+humXc=,tag:kdsSoiae/y+SCHXEsgJEKQ==,type:str] -AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:pghnhjqo8hd3qNrpWbfCRFWxoTw=,iv:vznb6J2VN/W1lUoAvD/r6Z99hkzcJdem75wWiEW6d4s=,tag:UZTpPmIMgcaThABQLWz1rQ==,type:str] -AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:gnCEFsqMpAIVdIlF0BAWuxCFGWs=,iv:l2BVc+myiioI/XWZCxRiRFFs3Xg4g6jx4nA+ycUpEtw=,tag:Bal4LYtDSipG+Mahi3Oe4w==,type:str] -AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:2div+Eg4zDzDIPgLJtObkjka2JRKAsbWZPTRL0FZgfFYsAovx63Liw==,iv:dvyGJyuMgj5zQHzdmoTk1rrYKKxIo2+kQuFwbCTm5CY=,tag:WjkI3qAlFwA8MwrUFVyuww==,type:str] -AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:z46Hakr2vI5gvWFpaIAK3Ql3DIoCw/lL47j7cd0giSQTh/ABxsJ/JA==,iv:kGlx7FjwdJJckKOWnBEZjoytOsgzFytpuWc2y84DxOM=,tag:FvNS10I5wpTrs3CPR6AHwg==,type:str] -BACKUPS_SECRET=ENC[AES256_GCM,data:AlfLtRkXyDqqZfu6wohDkcWtT0V6JIoKJ3z2Vz5ZMupFUBC5D+14aLZAMzg=,iv:KWoi+M42Nw92Fqetpq5ofr2eyP4L0SSOOCqbX3SwBOo=,tag:zoocybd+rcwDGiMMSQbmSg==,type:str] -CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:yxgHI3i36FFS4L0TdzUN1bpXLIHpf2OEDCnUgD9XgDGOxf1z3OQu/3QViRmFeqRWcEtppvU=,iv:EIud/glaSfEoZf51OMOLkzBteSjDPmRoI8ivyoIFfZE=,tag:5PtJoBaPnEBtSzBhvAwgwA==,type:str] -CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:VS/NWdB8sMVehnPUZ3Udpw+aO3Jactk/3LHo04Z7l0IPq+0tVy6Nut4RQUFtqAgXyzob85o=,iv:6GEbd0TGl2eYst6IhB3dcoPaY5x+LxtYTp7LPFNgjmw=,tag:dYqoHnquH19vA7oZ8xEVqw==,type:str] -CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:/D8G2dWVZuLTAI0vfbL+PIDmhOvwMsuQOtimeBBK9nY=,iv:QReP2lYx4uUjQIfduUxdNq1u8ASOjvPFgtK86u+3MOc=,tag:RQWhCVs8KGdhE0yu8GD/zw==,type:str] -CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:TGAwib9icDVyKupEv1nm9PCNNwmm0mA825ZtUl6DwLg=,tag:Sal4TWsNght8fIHf7dienw==,type:str] -DATABASE_URL=ENC[AES256_GCM,data:RUDE3AQcaV6KAn590pOwOl7qe87k5JP9URV0Zo8ASERD/Dka5nYxSwN/Dgc=,iv:z8K+k+RmM1dviYH4yGymshUh+RW89+65pi+kbNVUJuw=,tag:VjZrmiVBSUK1UuUv4Jz/Hw==,type:str] -DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:pmB/N3baHwHUvFsz7PriWNv1BtTGHAcRnbpmncjQJS19FA==,iv:cHr48cYEFnlsRs8gDXiKVmrjGETnizZEo08GLOypHrE=,tag:NWD4Cq5ZWRPqd+qxL1ZCdg==,type:str] -DOI_LOGIN_ID=ENC[AES256_GCM,data:CIIDza5/,iv:xuxBJyAT2bRzLsQVxPBZX2OiBt8U1UO0w5+vUEYRtDI=,tag:4rWD6V3RicNKvsJGwhhtrw==,type:str] -DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:vHEy/13TldjpwEDl6nqV4sq7VSU=,iv:WsGxcJfyuwBQBqR/YjhLJ5hW58OoQptTbvGH+vt1ba0=,tag:moDv//PwLAwjxCgq1b8Ohg==,type:str] -DOI_SUBMISSION_URL=ENC[AES256_GCM,data:fgWMFf/1ZqhdLUeBd+gEOHz3P26EpPU6oTTtzhK92nx5fO9K7XJf+0U=,iv:dqUlPW/NqK19Kmg4ojbg8sasJyJWxWqlGMPNdNguU7U=,tag:/l7OBHJMcFVVfJjpKKB4lw==,type:str] -FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:KdNtZ/QZfSrkEGbF2IO6JWAiSJx6b00mp/WnciZVWLk=,iv:04tjQifpUV5MBo6NJYNdVRS5/BVyhAl/L3gtpv6jGLI=,tag:oTos0XQAAlhjs76t1guGFQ==,type:str] -FASTLY_SERVICE_ID=ENC[AES256_GCM,data:YPXtwpSnGf/DbBnnRrmEP7aNc/G/zw==,iv:vxcyPgXXLe+NZS8v9o2JN1ORH/c+fEKrRkFschVQyvM=,tag:P4MnzQ58vJWIee9uP+xpJQ==,type:str] -FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:OcaGHN+EuI59SIKw4OZ2CrN+HC1lFjrHFbaEN6Hi5mY=,tag:hScOgvAJGVeSWhzDcltcFg==,type:str] -IS_DUQDUQ=ENC[AES256_GCM,data:O/gsEg==,iv:YNfBOpFYY7UMdOHpDf/WV34wQcvIxZK2YgQTKTwFZ7Q=,tag:FEbGo0u7Ym22lKUZEHL4mQ==,type:str] -JWT_SIGNING_SECRET=ENC[AES256_GCM,data:1XHMU1zNhoopCNSNWb7pZF+4mR3tdGiKEr0UU1P90i7TpiGMpKOfNrNrmIkk+udvSljHbDkIB41X9f0xmHvUk2wJ7nJikGHyUb5HRahWm4FJfbleec6mWn6xPJSV5Kx7Jfnbkhy2+aI5YkhLHed7sgiJEv+Q0aU1nyGsnS8zmOhgwgGp/UjwfNY2yaEs34tEWY71pTbaBOeS9VaX+Odeho4uvvSeNLcxrVDo7h/kXiPqMd+9Jdec/l+mIN5b/yQyCen2U441CyN3UwKjxIELdbocU5mslQ68y5As1r6km5sQkUfKUPF07iGGBeeTb0hqJQSJRQLN8gw4H45Ka5weolF/+Wz1Bl2IfQrcj7vZ3uVFiXfa96VryJCFCqDMX+jhUpS5BV0NrC4eusIdMODbRHRK3Wr5x+EkhS+BRGAf8rn3mp5Jux7KYBhg+KA1Bd4LduIqVU781HK6Nhw41palxv4IGZqn9HacUzdyise3KSPUVwtUGmXGi7s3/heBXkrXEDGXx0ACMWeWc7hKWC0ENSbmXm0hMk7VBtMscvH4t44BqZ+ECZEqqK321pal+dHoYeluXfMI4XU/ev6Ynd9qm9crZN3GHDZUM7wiYXXZPagn9sDosTKPbmOtw4jQn6hQE4aHQTgDf+WD0FUD6LUebhK1lP0HXKIxpir5lhOGccQSgUL4EMPXEmD+byEp5AyemmKy1xBzN8b0jUj+dtq1QtHaHosTDO9Cp7emuPA7e4bECeuVqBfwMDY8xMCFw7oOOqd03+8rNhGB67cEzx+ktijixXqvHJbBzzeK2D1LVl5GC5vdl2S53VzOyNtj5hUp4aZiYkJCooj/i5zEo5buAei8Rzbh+ewczzx4FK70qjByS+gdyxoJqDh7NE2i5QbgyeY/Rq8PNGmAV2TD/IyXTJx57TPoEoJkZmxkjqbjjRGNgEsdyl//t+rttlScsmjQ7qVLvaxNLl4JPYpOUPOWRNMgrUryDeWg3Z/EnJKBA7JJdbs+S9XanqFthESIDWN61tw597UaUXfuYSr/Ffv/+pq+OS7I/bRJMRZZip6cJC1dGpUt2M6pL5I6R2wgiinWCKUgZB0N20ozeZsOIUOF2uBQLEsyiLAoRaZ1U/QVCJog+meDiSh7zE6E+V4bvMnfE74ICDIlCl5HvCkr/6SFKkGT2yJjRI4fIlX+dt0ZNaqVzBt20vX+clGzLhEYdbh1J8pRQq1zm/pV3uoQBpqDt5TN0Q+edUAJKYF2+LepwVAG93/WdHkwP6mJJckU8v4TviIODUrI804JtUCb0SvoYRAb2j+LK4LZq4wIivoYovrBssJVEaTGKzmiR5PtZcsWD84dG5lbI6qk5fJY3K5AaQ==,iv:3mbAXwl4QGmjHGU4aB9QUlpt9KvQKsSWYMpaXi9ysW4=,tag:JjU4qZyuHK3Q1HKNjoEjsQ==,type:str] -MAILCHIMP_API_KEY=ENC[AES256_GCM,data:gOuM/Prm9bwa3yXobLCTNsCKbZ3jO/KWk+cCvMuQuNGclrS8,iv:7EBEv3SyFa3xs0J3wIHrwbIwSd1bejq+5wOS4Ays4lY=,tag:729R4cKLmAuml/hy+x5Gjw==,type:str] -MAILGUN_API_KEY=ENC[AES256_GCM,data:GnxtESWgeGP2MjQsjFQHQH5Snq2t4/G9caCpRXaBUoOO3Eyp,iv:se/jsTlvXSV2DuKVknXVZXtO6lsySj7zvzcmzHMFFGo=,tag:e2ec7U2dT+i40vNxkYL5yw==,type:str] -NODE_ENV=ENC[AES256_GCM,data:iLFf6vCev5kiEw==,iv:05NnJOb8WPCGiPOXzkZQyodMi1omKHbNemOgjZ8ZpQE=,tag:uNj+7bCOmGO8D4Y2V3PiUg==,type:str] -S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:PVyNAr6WAKO4js1Ocv78iFU2jsM=,iv:mQb6jYNoJbtPqI8K9V11dfiqyBgXCtBrNlF2S0f8DzI=,tag:RUNh9eIltlh2Cpk576Js4Q==,type:str] -S3_BACKUP_BUCKET=ENC[AES256_GCM,data:4sAN2LhI2mbaH+g=,iv:2zUntfNLSgclMPr2eeJZzxbRmCUDcn5nqaXEGQ/f9XM=,tag:rcll/08aCIB7SK0Z8m7bQw==,type:str] -S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:96eTnbhuGGWxzL6ykeNsayHyQvNqtCt+UGTuGJMOFkt2/Lw=,iv:qwevMnwR+vakTJW/TpXPRPDRevZSnLEpXrdwcG1GodI=,tag:VW05PcHalxwiRTOLc3yMKg==,type:str] -S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:qfQISkTnYWCbTVcJaIyy4hlkd+9wuxqxbfrcvbz41Lqv3yMBMaeptg==,iv:oEHr5Rb3lffBabX7cFyptQjOAtvtGWHXVWyw3/YpESI=,tag:OPWBEnr+EeWCvUE2/QTHQw==,type:str] -SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:GLarhotVHAlZbhK8cvnU+8VSt5wisz73FWCVktzil3BxhsxQ2KOzwzbPtHWa1UvTwy0rcP2LZdsnV7V2c5k7VzASlfC3Z8LS3HxQNv/78p3IIv3ywQ0owfV26s4OVvwBpMt58xH1Wx7vjnI8m/MuSYZRS7CxKuJIcL2HEnpIU6fUpLxvaAoffvOz1WG+HysCh0A3tiGDTV/bDwt9KBqOOxNY+qYmgYY8O0ccPNfWdHpusbYhuFGlJz79Zw==,iv:hg0In5HCufBdHEofe6gc6gUbrI2hd2TJfG9xlDkYgYI=,tag:/58RUX2CsLssG4Yd8GoSRQ==,type:str] -SENTRY_ORG=ENC[AES256_GCM,data:KnMS,iv:LoXVVgSgi8yxtEcAiILZoOW20DRn0YhhjTn7JfW0nnI=,tag:JUaJD3hOyGzQMrwABfVdsA==,type:str] -SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:ppc=,iv:+aCiASdlFM3P9oSx1r3rTdusdagt0wmFN8/3hNtO680=,tag:7y5KrA+iq5GDUZ+JnrW9cg==,type:str] -SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:F/ei0MQ6i9fYmFdiD4JgBxoVhB6y80zWOM36zHSrb+dYD6pHWp6eS02TzjbxtpzS5g2vhBKSl9mOD4L6q5gMRLQYQiXCIQZWqtXCEfUNSQ==,iv:N/3PyzJu4d8crIwUj/pNj851KTfV0Oyjaw8W7DnEjAo=,tag:qQt7chkB4M5aoMypmSJqig==,type:str] -SMTP_HOST=ENC[AES256_GCM,data:gVTlsn0G+AhFLFOxLxK8oMKYWe96NdlIOpC0DwVTe84ZTQ==,iv:srScSfqsWzn4dgbgKYd9z3SqxR+ymRZE//gBzX+73fw=,tag:0ZXFNbDQ0tKw1LlYspet+g==,type:str] -SMTP_PASS=ENC[AES256_GCM,data:56wzIiVMlBllselrpby/wZzD9sUQmlcRWYhOMgpbOeH/kbmfBtHJvFXbGI8=,iv:1YIdyFiy00wp58iLGoQOFO+JcTTCkuvGDgzB7nhvaCk=,tag:vREQ3leI5cBFILpCuTXSGg==,type:str] -SMTP_USER=ENC[AES256_GCM,data:VIpWGzlKNb/lcCiSjiJtd7vOMtU=,iv:JfyqNy1ipaHSt87N9uC1JfbJDpKr+PscOOpcjdZUpo0=,tag:7KcTBnoZeARwLm0I7E7cuw==,type:str] -ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:rx9rzHedBMIS4/GAjmjCXwCz3h8=,iv:XhnqcyHNRYBvx7+DZpc43k8DzM/VvztnziKW8dl2630=,tag:r+tWGKa8o18umkwuRbbV+Q==,type:str] -ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:feog2Hy8yngxKnbfwhP4VNz4YmM=,iv:VLBtulj7qymav2/4kUiuNja4tc2kBWPbuWWEE0qfmtk=,tag:sd680z4kuNk8mUjzIzM4Ew==,type:str] -#ENC[AES256_GCM,data:9r89kx3dfaK6GdKZVg7Y0OhI4u8=,iv:QX8i7/6PB4kfnUB5lPnbZw2pUOn1nIw2vHvhIxOb1kA=,tag:74LfENAk3IvQECE0ps6x7g==,type:comment] -KF_AUTH_URL=ENC[AES256_GCM,data:UV18YeLuWs0PYNp5oACK3wRqKIJs,iv:2uazkMr4+AGIWBqhzvEitAqCQqHPwdcnd3m23L9m/C4=,tag:fQU0BPaBnKiqQVbeUIe3Bw==,type:str] -KF_AUTH_INTERNAL_URL=ENC[AES256_GCM,data:uIJnZqR+IK66B+7k3xuiio59uk24FF+5Y0FdDRvG/fE=,iv:NEAfay/7ImliuCgB7PPNh4vPRWzbMdf+41YZtip+H5Y=,tag:Nl3uCR/QjyGFnhWG2Ms5LQ==,type:str] -KF_AUTH_CLIENT_ID=ENC[AES256_GCM,data:qRkkoQHy+DX7,iv:1jhXxKMGenzoWFRejJH9T0IzDkezzCc21STDJcM8ssw=,tag:H/tCmD9m30n9eVUC2h3bNA==,type:str] -KF_AUTH_CLIENT_SECRET=ENC[AES256_GCM,data:B8bo7z4N0zbPCQ7+1TqM3OWJLXmFOkYotnzMSiDCLIGFysrLpUag20aQvqJAz76AwEbryHUNh936MbmcXCJ3sg==,iv:6YL0UjxSY6hbciHofFAIOFfubPnR3WGyYQiKwLxllP4=,tag:7JjW5uy1AnYPqXKQUJLzEA==,type:str] -KF_INTERNAL_API_KEY=ENC[AES256_GCM,data:fCvQg2WVAYFSRWKZIECDN3Sy4L7Ki7vYmlkDixuwrUA=,iv:WOyPgNVagUqxBRW6ouDd0F57CJx7e4Tyyqahl+3if7o=,tag:Ztg2c7TSk1bT+g4blHJ8uA==,type:str] -APP_URL=ENC[AES256_GCM,data:uCl5JwHh/O7v6J+erNQKkfrzpoua,iv:lWu9gDqOhEuiQDCWCr2Ku0Irsn8JMz+VpLWmtR8l7HI=,tag:Z6477YmpoC0AuEZr8ipupg==,type:str] -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLbXY1ZVFLczFrK1JoaEpn\nZ0tDUXNBQWJQWTVSUjlCMmZyTXhDT2NiMEdJCjlDMFhJUVRhWjI4WVEvalRCcW5W\nUHAzYktPbEtLK2N2Z0NMc3Bwakd6WUEKLS0tIFVjNWxabTFTUEg0UFBvNWdtUEFz\nM24xNFZ3SU5RMmlOd3k1VzBXY09haG8KOa2DnFK2pCqALNx6Qmxe6mvHqVTJlnoC\nffAqsrvuyyTB3UhmjfP1F2WSJWRhxkvzFuOoNJvKfTKoIVojFlfuhQ==\n-----END AGE ENCRYPTED FILE-----\n +AES_ENCRYPTION_KEY=ENC[AES256_GCM,data:vIrqh66O0vIjl5yZufSBw4N0iFFa3aa8Ge4/7fsQaFu7FIaNJngDW9rY0Kssx3MeMTeTSRsQUYDKgEeuguUwWQ==,iv:U1HAoyGkJvJzsxWCvDZC+oasHj85wBFLO/KVQ/L3sEg=,tag:qkbB7uUlIYBLPtpq/oQ5wQ==,type:str] +ALTCHA_HMAC_KEY=ENC[AES256_GCM,data:4OfsN3DoUJW/S/IjG2u1CD6pVVCuimRUC1RLeWV3I+x2XLFVNVFMabRpSdWYi7A0D7Hemk6LDph3NXwrSu/yzg==,iv:ig3JqrQJ1k8QM8TEttythWE2eAbD7UAfSz+D7EWgePE=,tag:vHpOGbKI+a9Uyw8BAoboJg==,type:str] +AWS_ACCESS_KEY_ID=ENC[AES256_GCM,data:go7ZUa8BGf7XHwS9hg3ASw4bRt4=,iv:xY1SlZedf3zJPI5APuWkVAlElVVKY2AoLh19GjNSq78=,tag:mOakCQGa2m51we4/HId5VQ==,type:str] +AWS_BACKUP_ACCESS_KEY_ID=ENC[AES256_GCM,data:ux+kgdwZ89ob5NrOjX8gg6IFWEk=,iv:zEsH0RZF1oWkb8SAct1T6lLp9B0iPDs3IimN4h1wp0w=,tag:V/abHX/etKgXkq32VKbHWg==,type:str] +AWS_BACKUP_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:YTTl7jZpgj4zrkKBFa9TFQmUs2s5EBtUPrVmSJwhCYDAPEgG5cMvaQ==,iv:k2FkkDGsRO4ZBaIi8jC1KWHk9EWv1KjZ7i1INOXTmIw=,tag:1yeyZq+tmarD9Rh1ygwPrg==,type:str] +AWS_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:vQF57pHMG3xzLmyMmH0SSSaN/GHHB6e1Ys3GW6FEkK6pKMffwQVvOA==,iv:MIHd9fQG3jP3J67yGscANL249U/ZsGRoJ8zKvH1CXp0=,tag:LibPhBr9liC75tjinj1IbQ==,type:str] +BACKUPS_SECRET=ENC[AES256_GCM,data:zhX/bJBMNrEWGMvu9Cc4wRG4OIVjFU22kl1t0ryU0e5bmRx6D4u1hy9lAgA=,iv:LAyXrFZqxyYWKaz7bxTnF57V5TPFRqZTY10I+xyrEos=,tag:pgePnfjo+XxOHoVxYzlsdg==,type:str] +CLOUDFLARE_ANALYTICS_API_TOKEN=ENC[AES256_GCM,data:DyW7eeMeX1wJ/1+0FzeN2w/Wzg1kKAZ2JBCbcSwIKdmeSYZGTHCJ7tZRDOPqoz/DRabkDAo=,iv:O39bayrEyYSIiwWKdDo4R1gz2/WC+99QMgySV/CnM2g=,tag:q+yPo2RGjQDzQ6zvwkIl2w==,type:str] +CLOUDFLARE_CUSTOM_HOSTNAME_API_TOKEN=ENC[AES256_GCM,data:cLR9rTfrs6JRgRLBz96yMYt64YB0ASDU+4QissBpdHwrBuS8LqWkUDOl97x+IUL8yh/XHQI=,iv:SemyA3qERi1lQMrk3S5zMvWJn+R+zX/ELfRjlulHLhA=,tag:6Qz+l73w3KSVqFSDnWFtaQ==,type:str] +CLOUDFLARE_ZONE_TAG=ENC[AES256_GCM,data:azYYUIr+jjnc08bnQQwzIVc4HgxmVwG10rLEdMhJJro=,iv:s+de5cZF4/N1QrSK3bYXvr8Wo4AQka08cMzK5W+D8vE=,tag:TdlZqgBc+Y7hdabmK1g7uw==,type:str] +CONTENT_SEARCH_TERMS=ENC[AES256_GCM,data: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,iv:VOce1t4w1aH/6j78p8eOeN9ijamYZ0pgFWEkvkQBHz4=,tag:Kvc7d6lV55muFVebnbOaQg==,type:str] +DATABASE_URL=ENC[AES256_GCM,data:M3oNL5nTiB1vUt4lxnHVkuekQVgYcORiVZI7uHPepq2cIZFKjuf2uBfHfCA=,iv:R8Oy6X06lY0sP91yZMmS2sd6MyUzIX+5P9t0LZ22bIY=,tag:qzxOtwy4Kdab8jcWf+XA5g==,type:str] +DATACITE_DEPOSIT_URL=ENC[AES256_GCM,data:unPKZvOkD9NW5i/qu/+CJOeDw3A7/nVoV3T7Qw++059Fiw==,iv:cO9mI1M3yZ+6WD/898AU4JP2w9JAiNf6Dfuku57xygA=,tag:Osnh7hZ5O5/Vhn3vw7FKSw==,type:str] +DOI_LOGIN_ID=ENC[AES256_GCM,data:692fnKaD,iv:YoJ43peJggSsAhXtInv3AEuHX5uCxqUR2AM0NYNu9Cc=,tag:MJD9KJPtr7MDO0IP5QuoCw==,type:str] +DOI_LOGIN_PASSWORD=ENC[AES256_GCM,data:/zZsrJALUEVltMS/XK5n1gwn4KQ=,iv:lpRBEBlQvvg7lGuddPmqgqMCVgHaacQK3ToNwnlLzOw=,tag:7Ra1Fi3Tplq+AbC3PusNKw==,type:str] +DOI_SUBMISSION_URL=ENC[AES256_GCM,data:afGWmXnlK2v3ovsNWC6LXyyTN2SZkGxyRtmIrCY+PhWdmnq/kgeehGc=,iv:H1bZ4D3DvEWrFPNtYQjD9/ES4qd0UxD+VJ5pJkqm9iM=,tag:0+IB/7bifExa0Y0M9aD5Aw==,type:str] +FASTLY_PURGE_TOKEN=ENC[AES256_GCM,data:+o4L7IcjDsJFmSvU7TQX5hFoDHXuviZi+yiLoOw76sE=,iv:wJTcoR4hs+IS7DmsfFsPy1FV2wNZHugpbkPQ0PlA8MQ=,tag:/gpfMK/9vaDCq31uD5YKxw==,type:str] +FASTLY_SERVICE_ID=ENC[AES256_GCM,data:N6oV1j+eV5/hjTO9G5Cic7kWGZJ1cw==,iv:ZLYJpjSrx5r9hYgf3g6OXAzuPKxwoaIrmWwnvkGSQcs=,tag:jrI6eQuEKniYerPGPYn9Rg==,type:str] +FIREBASE_SERVICE_ACCOUNT_BASE64=ENC[AES256_GCM,data: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,iv:Us+CnNTBLETpGwtnPs/2KROsUGbPpknnu2kb8xtlMNk=,tag:SfX/hQIc8lSgvVZP3Qzsmw==,type:str] +IS_DUQDUQ=ENC[AES256_GCM,data:U1kWzw==,iv:HX2tO/dJrRHNVTG7WEhDzSJ1TgdRAijUHpDfUEAZYHY=,tag:+O5bbrsOODLwaapINpvJwg==,type:str] +JWT_SIGNING_SECRET=ENC[AES256_GCM,data:rzMTJ6WaNqnTKjbgE8ympwqq54uRSGhAB6XfI9kPVoCqZTgV1dROMSdgWKzPg8sb8kiAMEZW3hZXpfoMRvwi62tIBIHb2wRI5hkrkGGWEXI26pQaOpWQsHDQ/9Z6wBQnhX0TZ/vR7c2/9t1X89K86y25nE3WuI4ZJNrMD619xw4wcSkk6DycDV/KwfMitW5MmsP+YMBHtawMQcPvo6rk6N1vvroyry6pGlfXcUSaNrcYm078SnZTRP1kp2kXhybCbGvZo/aCBbvujQcBtGOd6GoZu5KbNSfhmWmEK6ox8C5uUy9QAUqfb+4k0+6WWtivN5ZXaGZNkQqUI9kyxwtq8V/8aGZ0BCsWvUocOHvUdufL/VoJhpEtgSB3TRv3OqEVTtmm7c3587ML55KP86E0kcjpiVLymVXVfDnaAyq5uhKLmea4l8j8mkpzkVWltTji3HWVWnhLEZDMRQa4xobfHyalOQOA0Hi6UW2i9PLfiYuHNO/86qi7+boChT2eAlgX9L68vC1sNXc4mTtcDtnuuLNKzb7UrJDlvG07VRidXvXmBoKjwgoOYfd/8p2PXhOklk92OhTR1ElG47y6/a69O6/YhSq0W3IoUzAuvtO6TN6BtjV7ONrOM4/HPN76nEKcaxh/DFnoacn6Ocx7Es4BoZ++L5oFSB+swTcA13BLPIXF95+D2kP7icvjOjKxazxJkgV3rsfa3+Vmk3clnynnPgU5puzgZ/mkrvz+elbpfe83DYFfYdMm28vt38uOyjBU0prytY1rMPrZRhRW8wOYj18FgKMNVOQqu1z8HOzqcJN3E151wpgoM5VhTYnJ1CUjpOcz46g+4NfZD0mY6kRuRJHh1ylojom4IOixcomB+l4GVB/jqoAyp420aclnkqc6pSue2qE29RcjiR0Gpu7VpGKd1aAKbGrfUCas7pxv8/WmPJ5oT8JoAn90KSSauAJ1ld7yJY6sd9v05EPJ4q/pY4ql5t0sK5LPHdhbbvd99r/VY9EmJ+/rDOePvyiTm3K7L4xsCwKi3XilmDDnJP5VsY/53gqlCziK4MqHaGt5bzhutqQZjt4Cmo/RBetydKQvkLHkEDiLaxwajb6JFLH7FNt412aKDepbItF901jYyN5QEDmaXQC4gywLp1BKQIcL+dfHi1uN7XZVaVSYY5F8SZ45a6DVTqas7fQpopDgSl4EHkbRVFFp0qh0RATlI0RT2OfWfxA6DSlrCqu9n8T31yZU90V8y2vqDU0A2WuizIueeXg2aorprU70ogRLaDG114J9uMJ7xyEpnkb+kz3mJGaqZfntUnNcEZ8kLeiA8qKk++9HkqXdZV9W/9QLSfxzqAFYpaTVipmmHTvFucsRzA==,iv:Oe95yrYzqKonTZC9YO5k7QXBmx3Sa4GEpyiFJzovgHw=,tag:UATqruNo2htBSIxywSIVAw==,type:str] +MAILCHIMP_API_KEY=ENC[AES256_GCM,data:6NmLHYMAm31e/Oa2EIxG3s09fKrTT39nverHS0ZrbHd0ijLH,iv:AiikD3j+u1oaOty7J8fwYsWW5H/6K1+03XEgKqz4KcI=,tag:0BJWnxeCdHG0Lmrha0eTUw==,type:str] +MAILGUN_API_KEY=ENC[AES256_GCM,data:2sN86sd16WtohlMDkjQ0yKuvGgr4j+xIuiY8ULyTA4Jcs/p/,iv:hlTl+AR6O9xtnnykGLTAuI+q3AFMr8k6aJ3Qa2FtlcM=,tag:yMsW1h//6ZlKiifXhK/Y3g==,type:str] +NODE_ENV=ENC[AES256_GCM,data:W2zO+aQ3AQedyw==,iv:V15cRHUEIHVy0Y/nVJutv05OKqjwGD9VmAG6Mt6+xe8=,tag:jPT6EgvFks3NGtKQZq8HlA==,type:str] +S3_BACKUP_ACCESS_KEY=ENC[AES256_GCM,data:IMO8So3hBgfBlqCV1m6FZvmo3uY=,iv:14Bq4IQE7I6LP4XeZdu4Nq8MKkBm7/uaL47gFqIVQ5s=,tag:V7mJq7liFavpyetLuokMjA==,type:str] +S3_BACKUP_BUCKET=ENC[AES256_GCM,data:qW5FvcAneutiqXg=,iv:Czx/nxOW+usXbKREDD3fXACZ4agR5CfOlvRca4hqai0=,tag:PNF6sbkagOETqfbZFQgC4g==,type:str] +S3_BACKUP_ENDPOINT=ENC[AES256_GCM,data:qbqH8J8cJghBiNG+Rrb5CB0ltCxW1kp2I3MARAZB9Gt0bbo=,iv:aPsyr2nrMneu1ng9dEs/AlRktyXaj36SKIIGzTE+KHM=,tag:OpiH7Iparyn5X16/W33AdA==,type:str] +S3_BACKUP_SECRET_KEY=ENC[AES256_GCM,data:S3gFLILtjNIW06pT8CZOJI9VRBmNQn7FiHp/qPUSRstOZmoMRMb9sg==,iv:eJJ4g/afVUiJgGgFl31JvOs4Aw7iCD0dqyD59V4M6wA=,tag:keLFkQoMt5I6xePQDcElbQ==,type:str] +SENTRY_AUTH_TOKEN=ENC[AES256_GCM,data:k+7bpMGRHSjjfkn4um4nj7h4Zz7ha6RZpFj2+tI8HTyoJRsmMWYwYoE7JM1tnrrkLJPX5v8Z8Gx0IyYpYlpdtQohGGzEqmV9FG0weLI+WzCOMMi4j4fHzQRLC8W/QCJYvUhY1FcSw0qTtJXkGqSwrJKMs2XSkwN8SgG6PQGhnoqQKFBG88SQWhVszkPc/X+qVslGykcMPURnUihTyIFf54UW5kLrCVpmZ3Wk/gmuKQ9YNQNaaV+EMWR5CQ==,iv:Crd3/4Ish05R3nKDd0SxW6u26rVr9SHHTGfsIS0Kd7E=,tag:HGSAd2KTUtjSyq1lBjsLyQ==,type:str] +SENTRY_ORG=ENC[AES256_GCM,data:zAGf,iv:sMPG/+O2lap1vfx+jIHRvPQ3/bGdCywABnZfvJpBAPI=,tag:DyLuuy2svsQlNz9yG7Hhcg==,type:str] +SEQUELIZE_MAX_CONNECTIONS=ENC[AES256_GCM,data:C5o=,iv:te0B+QUWnIOwnBhKq9lRpUhkzpwqm9ykOm5VKaXqEKg=,tag:6IzPC6t+VxVKt8f6vl0dig==,type:str] +SLACK_WEBHOOK_URL=ENC[AES256_GCM,data:PaZb6Tnx/EuLix8o1NlLXSdpXeHoRHTxaz70Nkw3vgomscx9dT45UOUqMI0Mz8sRcW29YMqr4G1LbLO10MOwlkgJpECuKsdvbG1B/uTa3Q==,iv:Qak+46OGRxwtk7br6EAuRHn3c2FxBiODugoT37htNjU=,tag:Jsfs+rGRWgA903N3EguHaw==,type:str] +SMTP_HOST=ENC[AES256_GCM,data:/HGDBiGkHHBfuS8zmvfZqq3bFnZ1z/xiag9vlPjT2KrwCw==,iv:iwQCsrrs69jwQW0RoHXr3GT7WSU7rGlUSe4b+MLY6uQ=,tag:BTy3FvSKRhdy54Bwk4Gypw==,type:str] +SMTP_PASS=ENC[AES256_GCM,data:PzXVffQ4YzY13mI4DZTpdaTivRKKXPoNCOKmrfUVeYwhXuWjOeRPr6xyOZ4=,iv:L3THYQEciHtKVSLeyzcUHoDjEGvUEeXQutLnNkbIxTU=,tag:lK4vvOxLg/Z1yUBJnjWrdQ==,type:str] +SMTP_USER=ENC[AES256_GCM,data:b5mxkDDZn8cHzzxqg3L9yiou4q0=,iv:e4wWIYD6HpV38wAjiEr5OtD49xcP9+hAhT9fREHJ/bY=,tag:FD62PoZxRxk/UIiP6PRFtA==,type:str] +ZOTERO_CLIENT_KEY=ENC[AES256_GCM,data:GQ8X1rsoxzkizLXvo0dlr6AEHY0=,iv:a8fIPhKBBqFiWmYqzzK4P3nsQyghUAVjTziZV8lEfqw=,tag:O5Mu5eKl61vehsKtOERvMg==,type:str] +ZOTERO_CLIENT_SECRET=ENC[AES256_GCM,data:tP27hLtMVBtvNkp7OZwnFgnCMgk=,iv:dCfCJ14DzqvGIun3w+WkBTw4ZtXLffxdSH2+w/gkpVY=,tag:0tdEyObg+GeW7JKyGZrgGA==,type:str] +#ENC[AES256_GCM,data:qkhNcd2cpoR7xna7lSMHVYyRkwU=,iv:xuTXXggrRr1SIxoL4g8g7aHNq3Zn/eMZDPGzpoWJSPg=,tag:YrHS3JWvk6AMAaKP7T6d+g==,type:comment] +OIDC_ISSUER_URL=ENC[AES256_GCM,data:Y6ZZZg/KO46M4xN4Itrk+/RKVqch,iv:QzhlB7rzUYQ0qvDQWfpO4g3cAFr1CZv/kVWh8hkTZEo=,tag:jrqQwc6qnAFicPpCocAoDQ==,type:str] +OIDC_ISSUER_INTERNAL_URL=ENC[AES256_GCM,data:GLZtsu+CeDo3bLg+qpnPSr8Vfsxt8Q7SMkkK0HBOgR0=,iv:nLZwEdRR+Jbleut3ydLcK6dkeeXGQr19VKeOWCCMRU4=,tag:n7U5+a1PqTaB/821Tl2HHQ==,type:str] +OIDC_CLIENT_ID=ENC[AES256_GCM,data:u/QSy8R47Bkh,iv:M5UDl6iM57cWfNfbS7c77o13il4PoKmEBhughUA4b/o=,tag:4ejq5eBXuPv4pjeZslgejQ==,type:str] +OIDC_CLIENT_SECRET=ENC[AES256_GCM,data:1owOMDVHC3wU5lISzIUvKwyHvyHkLKg36MRmctWRI78iFK7wI6tKebaQmIOwwlLdvjoVP0wk6nr8vFZHYexIDg==,iv:aN4v5vsdkH8qA9YwsQQXvqDXszqBeUkAv9OQixcqqUA=,tag:wPcjLdEeEivqaX4RgbMM+A==,type:str] +AUTH_INTERNAL_API_KEY=ENC[AES256_GCM,data:VcT4eqKsqoLmYWqJU+YCQxNYjangyvFfslBePwROz8Y=,iv:GyJxeKs6ZO3ZXggu3m9Pc6GnBlXjoCU33cBb3SPrcc4=,tag:6IziFPglbwIPXmURwTeyXQ==,type:str] +OIDC_ACCOUNT_URL=ENC[AES256_GCM,data:AfoxH2PbtJMT1YiWgVhu+zH8PeGn,iv:+n5wB8teZAQ1abEKZOpJL2/yQCW22GHglYBWMZDKju0=,tag:+EgWhvD22GWPQrE19VMCOQ==,type:str] +APP_URL=ENC[AES256_GCM,data:nzoHDC1p4LOJiT162qcS6ejrj2Zm,iv:206a8lmEpzWDQhI+5ASqwWg/a9KAs3MnWiTwjQ3YLCA=,tag:H3i1tj0xeF6rAxtR2sIVtQ==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxWXZQalUyb2JaRFZzY2lW\nMTZzN2JxdWdZa3Z6NE9mNjZhVFBXaEdIUjJzClFlTjF4bDFudHIvMHdWQVVyWDNF\nY3Zvc29JUHpRaDluazlDTVROeTBiS0EKLS0tIGRLRFpkVDBJYmhUdkhpSFBSSWdC\naGFHNk1yZUxhRUtGZkt6UFYrUGc2Nk0K8MU6bWI+2I1BFuxrgJSA0yuHzIES0XaF\nSpGQXVQ9pSuMzXVc7FuiEBEt6iQN/rfMhgeNSlDH3/6P/s7btfakpg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1wravpjmed26772xfjhawmnsnc4933htapg6y5xseqml0jdv8z9hqemzhcr -sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWGxwMi9FUzkyQU1OemVx\nYnlSL2tSdm91WFpzVHYxV0lkTitsbFV1Ylg0CnAzVzJaZytDM2E1MnNoUDdiWXZy\nM0c3NE9nK1pXTTBvNCtqOW8vU2RNMFkKLS0tIFVnTU5tMkdNa3JoRmgwaWhNQVUw\nYXBTRTlhRk5YV1ZnQ2syMHVZVnl2ek0KPy9O6dvlcWbkdnXVeR7z0pON9dLqXuPh\nSg4LyILguj83IGChxv5ijA4gsf+6FK1fB1597rwE02FWek/wFHRrEA==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBVkFkbERML3JjK3VQcVdK\nT3JUMUdWbG1ldm5NTFRISjVSR1RlUEpMUFZvClR5K1BuREMxRmZoVVBPYTRSbm1R\nYmphWTlXdk1FMER5TkdnMEhiR3Jya00KLS0tIDhJMW1HcDBBcm94M3M5VWFVOVNs\nNHY0dFhZT1oxckNoQzR1amx4QndWYVUKgAaoyraxVST7kBjDgtJOvZfaHmP9X785\noOic81S8FsZjmDjE5d6VoxsHp62pjDkUGzyqiSGIjAjKczXiLo7ewg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_1__map_recipient=age1vhftscteyrwphx0jpp0yl60xxrjs77jq05mkzv88js7ckc926vcqepp2cj -sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUdmR6alcxTGExSUJSUVVE\nRUliQVFVOW5TaXNaZUgrU2Z3Y2swZ2VxM1djCnNhTVhjMFJtclpiQXFCRVZjckl6\nQURZOTZienVlTldhak5lTCtVcGQxNjQKLS0tIGZ3Rk5GZDBYcVBMbmVVQkgvd1R0\nQk5zem5COUJvTG02TDAwZTBvNFJIMFEKVSk4/xkCx2XtGVINAGKd305+C3f+7KJh\nF/k5Rg5YxrQoKVpfN8npC6CM6iW7TbIeN6q3A4wpIrym3SwRXJRA3w==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_2__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSdDdnM1gwR29XSVZoOGF3\nd3Y1RUhpdzllWTlZUXNDTUsyVm9HWWE3MWxJCnhTKzNJY2tKWDdyWXhzR2Mzc3c5\nVURyUE1hZjRranU3clhkVUk0ZVJhbHcKLS0tIGZXMzArS01FYTRRSU9NVmVXU0ND\nZlh3R1JaeXVQcG5ZQ0Y0SUp5NGJrVXMKe/Oo71jhPQNm3owxPHz9Xec9JbqiEjnV\nay9cV5nMINhEPfK3e/R94KbzJbKzbmPDidQPBuMunJ7b91GetspTKw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_2__map_recipient=age1slx6e48k7fre0ddyu7dtm2wwcqaywn5ke2mkngym3rpazxwvvuyq9qjknk -sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXOTVKRmpSNW9lYmdlS2dE\nWmxrcklMbWJrMnY5VFhKY1dSU1dLQkxvVlNRCitaWlloRGM2Q0ZEa3JleEpLaEww\nU3JQbDJYRjJZK0U2MUJmOGFWRXNjMXMKLS0tIGcvQWVaMFZDVG9XSFRERURJcVpv\nc0tpQm9IQWFoNitxejFXaFNaOExxSUUKs9CvxdSQCTgT4CqoA3A2E34PKpvSqkhT\nWl3wB7VfX7gW0yKnTZYWwT0oHDi8ihXk6wU6mgI83h1s0pZgItSZgQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_3__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUVG8wZmgvcDBHNUhOUVRl\nVzRha1JRNjhxVkFCclE3YnBPT1liSmlPcHpjCk1ySDNZcEtUVEhBQ2RJNEZWaGRF\nK2VIS3UzWG82cEhJV0w2eit3b3RvVmMKLS0tIDFMUFZZZzlWZ2FrWVBlRE9ucUdI\nczB4RXliZ254ZHdPbTdlMklySnE1TU0KQKS4xxMWocaTI/7mJkoKLzxaBRq9pTKt\nzwnN4ywFYcyWuNa/qs/ZO3H3uUTZC4DAHnKz5thcTUfTrT7tc9Y8lw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_3__map_recipient=age1vfhyk6wmt993dezz5wjf6n3ynkd6xptv2dr0qdl6kmttev9lh5dsfjfs3h -sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXQ2RmMGJYZThRTG9DWHM0\nbnMwWUNReEVvNHZQVkY4bHpYMEJmTVlYZkNjClQrVHl2anZRK3Jxb2crT3ZMVWJz\nbzFoT1dHSzk1TE5nWjBuRk8xZ2hZdDAKLS0tIE13U1h6QzAxV0pzNU94RCtRV2Iv\nbE9xMk9NVHY1cndUcmdYK0tNcDQycHcK+52eaN10NAi0zGLjQwJCgxdolSgBH6HG\nGZQs75Vi4y/0gOcE3cL2aqPYMlCrVdgazLODEhn8qoDFjTvYSqwSFg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_4__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWL211K3h3S1NPZm1nb1BG\nRWYza2R3OWRLMGFRNmtMRFRYSjdlUVZOc3lnCll4bGxpNVN2UFZ4ZEJnbUpxSW9D\nNFU5UTZhZVk3UlVSemVvbHZVVW16TU0KLS0tIGtnTldpM0VzcmVnMk9jOEV2c1lY\naHNOR0RYTTBNR2ozRmFod2RrWS9JdVEKgjsrRHjM/07zkVOHeTrpE49/32LOYSAQ\nw4YO0oD9G+Riv0hB+bR6K/XmvJDlfu/QOkW1PgkimIDQ7ZEnl1iiPg==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_4__map_recipient=age1jwuvzyghfer7rx3qtqa4vs0gxyff0sg6pqgmqvp5zlhnpmvrkdlsdha4kx -sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1Z2pNaEhFdGw1enZwK1BL\nWXNHN1VvdlFKakJKVGVMNjdrVE9tcTFwV1hvCjgrZHNpclFaMU1JdWlvWWNXZ3JF\nKzhGcHpVTlUvcnozVEUwdEZ4cGtTYVEKLS0tIHVmMlpvbm1KRGNUbTRZd3gzaTlP\nNlVka3pFVVJST1FVNTF0WDUvL1lHaVUKu/p2zf717p95Qwpsy0DFFzFki3Tj+T3V\nKCb0qMq+uhq/1wzOXNp15oqWmYHWeEbxzOiOuAPGajd8bv7+CyMQMg==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_5__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsVkxWRnlxRjVqZ05kSDIv\nMlFSelhjMVJRVHpWeDlKQzQrSGxWenJmdW1rCjJiUWJZTW1yZkVUb3M1aHYwaUVI\nK0NmSGwrQ1dodzFWcXozaUFFWURuWEkKLS0tIFdteWYzQ284aWY3M2lSaDZSWVBG\ncWtxY3I5VkMwYmxYT3h1dm1yYXQyakkKW8obLgS85L7S3sSz4SNmBab34n2HPpMP\nUfwSIEBK50BwZxNikL/exYQq8N537xX/CbLR/9Z5kXjSg+aciI0cuw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_5__map_recipient=age1pgxk292zq30wafwg03gge7hu5dlu3h7yfldp2y8kqekfaljjky7s752uwy -sops_lastmodified=2026-05-18T03:04:04Z -sops_mac=ENC[AES256_GCM,data:hxv3pWG3ojHyAo1vVf17zvUtT28Jzqt2aEaE8bmqeDoXe/Vhq4SgU3OejmCBUMP7mkEMDjSTpixqA0yeXsHlF9bWCWvlCTQGCGpwiQffJbu7u1llajqHjobpEHdbFtgLHA/T2zKpAKoknER/VTn6IzKdzWVkFv6Sg+LRJhH17ig=,iv:iNqN1A5VBVCBsze1ZN2ba3V01esd7MUiALs1gcPWp9E=,tag:Ov3Kz+aqOMcT8NjwnyrXrg==,type:str] +sops_lastmodified=2026-05-20T17:17:29Z +sops_mac=ENC[AES256_GCM,data:uCaCv8548cz57JodHdPc7UUqGpkELBQIoaJpGYivXnLFWz42C4xHBsWB/jJ5cEdSz5JrLxIJdslIpZt+hhAe7PKry+YInIficoVfU9MIzo3FQMJxkrED9wgpNmn3NWWa/S0U00z/qp6KNVmk2J7Bqt3JB6MU/y50ApVyJ7rwtvY=,iv:W0mAYXsBM4M40mTmFrz6kRjU7DE9gBaMA7TCx3JskFQ=,tag:jM1+R5/N6ZTjR7xil3hcSQ==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.11.0 diff --git a/server/kf/api.ts b/server/kf/api.ts index 2359288df..12003317c 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -92,7 +92,7 @@ export const router = Router(); // ─── OIDC login ────────────────────────────────────────────────────── -router.get('/auth/login', (req: any, res: any) => { +router.get('/auth/login', async (req: any, res: any) => { const communityHost = getCommunityHost(req); const rawReturn = req.query.return_to || '/'; // Validate return_to is a safe relative path (prevent open redirect) @@ -109,7 +109,7 @@ router.get('/auth/login', (req: any, res: any) => { // Pass the community hostname as context for per-community branding. // The branding endpoint resolves hostnames → slugs. - const { url } = buildAuthorizeUrl(stateToken, codeVerifier, communityHost); + const { url } = await buildAuthorizeUrl(stateToken, codeVerifier, communityHost); return res.redirect(url); }); diff --git a/server/kf/auth.ts b/server/kf/auth.ts index 17f32ac5f..7866033df 100644 --- a/server/kf/auth.ts +++ b/server/kf/auth.ts @@ -1,184 +1,24 @@ /** - * Lightweight OIDC client for KF Auth (PubPub edition). - * - * Two base URLs: - * KF_AUTH_URL — browser-facing (e.g. localhost:3000) - * KF_AUTH_INTERNAL_URL — server-to-server (e.g. host.docker.internal:3000 in Docker) - * Falls back to KF_AUTH_URL when not set (production). + * Legacy re-export shim for PubPub's OIDC client. + * New code should import from './oidc.server.js' directly. */ -import crypto from 'node:crypto'; - -const KF_AUTH_URL = process.env.KF_AUTH_URL ?? 'http://localhost:3000'; -const KF_AUTH_INTERNAL_URL = process.env.KF_AUTH_INTERNAL_URL ?? KF_AUTH_URL; -const KF_AUTH_CLIENT_ID = process.env.KF_AUTH_CLIENT_ID ?? 'kf_pubpub'; -const KF_AUTH_CLIENT_SECRET = process.env.KF_AUTH_CLIENT_SECRET ?? ''; -const APP_URL = process.env.APP_URL ?? 'http://localhost:9876'; -const REDIRECT_URI = `${APP_URL}/auth/callback`; - -// ── Symmetric encryption (AES-256-GCM) ────────────────────────────── - -/** Derive a 32-byte key from the client secret for AES-256-GCM. */ -function deriveKey(): Buffer { - return crypto.createHash('sha256').update(KF_AUTH_CLIENT_SECRET).digest(); -} - -/** Encrypt a JSON-serializable object → base64url token. */ -export function encryptPayload(data: object): string { - const key = deriveKey(); - const iv = crypto.randomBytes(12); - const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); - const plaintext = JSON.stringify(data); - const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); - const tag = cipher.getAuthTag(); - // Layout: iv (12) + tag (16) + ciphertext - return Buffer.concat([iv, tag, encrypted]).toString('base64url'); -} - -/** Decrypt a base64url token → parsed object, or null on failure. */ -export function decryptPayload(token: string): T | null { - try { - const key = deriveKey(); - const buf = Buffer.from(token, 'base64url'); - if (buf.length < 29) return null; // iv(12) + tag(16) + at least 1 byte - const iv = buf.subarray(0, 12); - const tag = buf.subarray(12, 28); - const ciphertext = buf.subarray(28); - const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); - decipher.setAuthTag(tag); - const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); - return JSON.parse(decrypted.toString('utf8')) as T; - } catch { - return null; - } -} - -// BetterAuth OIDC endpoints -const AUTHORIZE_PATH = '/api/auth/oauth2/authorize'; -const TOKEN_PATH = '/api/auth/oauth2/token'; -const USERINFO_PATH = '/api/auth/oauth2/userinfo'; - -// ── PKCE helpers ───────────────────────────────────────────────────── - -export function generateCodeVerifier(): string { - return crypto.randomBytes(32).toString('base64url'); -} - -export function generateCodeChallenge(verifier: string): string { - return crypto.createHash('sha256').update(verifier).digest('base64url'); -} - -// ── Authorize URL ──────────────────────────────────────────────────── - -/** - * Build the URL to redirect the user to for authentication. - * `state` is the OIDC state parameter (encrypted payload with verifier + routing info). - * `existingVerifier` allows passing a pre-generated verifier (when it's encrypted in state). - */ -export function buildAuthorizeUrl( - state: string, - existingVerifier?: string, - context?: string, -): { - url: string; - codeVerifier: string; -} { - const codeVerifier = existingVerifier ?? generateCodeVerifier(); - const codeChallenge = generateCodeChallenge(codeVerifier); - const params = new URLSearchParams({ - client_id: KF_AUTH_CLIENT_ID, - redirect_uri: REDIRECT_URI, - response_type: 'code', - scope: 'openid profile email', - state, - code_challenge: codeChallenge, - code_challenge_method: 'S256', - ...(context && { context }), - }); - return { url: `${KF_AUTH_URL}${AUTHORIZE_PATH}?${params}`, codeVerifier }; -} - -// ── Token exchange ─────────────────────────────────────────────────── - -interface TokenResponse { - access_token: string; - token_type: string; - expires_in: number; - id_token?: string; - refresh_token?: string; -} - -export async function exchangeCode(code: string, codeVerifier: string): Promise { - const body = new URLSearchParams({ - grant_type: 'authorization_code', - code, - redirect_uri: REDIRECT_URI, - client_id: KF_AUTH_CLIENT_ID, - client_secret: KF_AUTH_CLIENT_SECRET, - code_verifier: codeVerifier, - }); - - const res = await fetch(`${KF_AUTH_INTERNAL_URL}${TOKEN_PATH}`, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body, - }); - - if (!res.ok) { - const text = await res.text(); - throw new Error(`Token exchange failed: ${res.status} ${text}`); - } - - return res.json() as Promise; -} - -// ── UserInfo ───────────────────────────────────────────────────────── - -export interface KFOrg { - id: string; - name: string; - slug: string; - type: 'personal' | 'shared'; - role: string; -} - -export interface KFUserInfo { - sub: string; - name?: string; - email?: string; - picture?: string; - given_name?: string; - family_name?: string; - 'https://knowledgefutures.org/orgs'?: KFOrg[]; -} - -export async function fetchUserInfo(accessToken: string): Promise { - const res = await fetch(`${KF_AUTH_INTERNAL_URL}${USERINFO_PATH}`, { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - - if (!res.ok) { - throw new Error(`UserInfo failed: ${res.status}`); - } - - return res.json() as Promise; -} - -/** - * Fetch a user's current KF orgs from KF Auth's internal API. - * Used for the ownership picker when creating communities. - */ -export async function fetchUserOrgs(userId: string): Promise { - const key = process.env.KF_INTERNAL_API_KEY; - if (!key) return []; - - const res = await fetch(`${KF_AUTH_INTERNAL_URL}/api/internal/users/${userId}/orgs`, { - headers: { Authorization: `Bearer ${key}` }, - }); - - if (!res.ok) return []; - const data = (await res.json()) as { orgs?: KFOrg[] }; - return data.orgs ?? []; -} - -export { KF_AUTH_URL, KF_AUTH_CLIENT_ID, APP_URL, REDIRECT_URI }; +export { + APP_URL, + buildAuthorizeUrl, + decryptPayload, + encryptPayload, + exchangeCode, + extractOrgs, + fetchUserInfo, + fetchUserOrgs, + generateCodeChallenge, + generateCodeVerifier, + initOidc, + KF_AUTH_CLIENT_ID, + KF_AUTH_URL, + type OIDCOrg as KFOrg, + type OIDCUserInfo as KFUserInfo, + REDIRECT_URI, + type TokenResponse, +} from './oidc.server.js'; diff --git a/server/kf/oidc.server.ts b/server/kf/oidc.server.ts new file mode 100644 index 000000000..051b2b899 --- /dev/null +++ b/server/kf/oidc.server.ts @@ -0,0 +1,287 @@ +/** + * Generic OIDC client with auto-discovery (PubPub edition). + * + * Reads endpoints from the provider's .well-known/openid-configuration. + * Works with any standards-compliant OIDC provider (KF Auth, Keycloak, Auth0, etc.). + * + * Env vars (new canonical names with backward-compat fallbacks): + * OIDC_ISSUER_URL — browser-facing issuer URL (fallback: KF_AUTH_URL) + * OIDC_ISSUER_INTERNAL_URL — server-to-server URL for Docker (fallback: KF_AUTH_INTERNAL_URL, then OIDC_ISSUER_URL) + * OIDC_CLIENT_ID — OAuth client ID (fallback: KF_AUTH_CLIENT_ID) + * OIDC_CLIENT_SECRET — OAuth client secret (fallback: KF_AUTH_CLIENT_SECRET) + * OIDC_ORGS_CLAIM — custom claim key for org memberships (default: https://knowledgefutures.org/orgs) + */ + +import * as crypto from 'node:crypto'; + +// --- Config (with backward-compat fallbacks) --- + +const OIDC_ISSUER_URL = + process.env.OIDC_ISSUER_URL ?? process.env.KF_AUTH_URL ?? 'http://localhost:3000'; + +const OIDC_ISSUER_INTERNAL_URL = + process.env.OIDC_ISSUER_INTERNAL_URL ?? process.env.KF_AUTH_INTERNAL_URL ?? OIDC_ISSUER_URL; + +const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID ?? process.env.KF_AUTH_CLIENT_ID ?? 'kf_pubpub'; + +const OIDC_CLIENT_SECRET = + process.env.OIDC_CLIENT_SECRET ?? process.env.KF_AUTH_CLIENT_SECRET ?? ''; + +const OIDC_ORGS_CLAIM = process.env.OIDC_ORGS_CLAIM ?? 'https://knowledgefutures.org/orgs'; + +const APP_URL = process.env.APP_URL ?? 'http://localhost:9876'; +const REDIRECT_URI = `${APP_URL}/auth/callback`; + +// --- OIDC Discovery --- + +interface OIDCDiscovery { + issuer: string; + authorization_endpoint: string; + token_endpoint: string; + userinfo_endpoint: string; + jwks_uri?: string; +} + +let discoveryCache: OIDCDiscovery | null = null; +let discoveryPromise: Promise | null = null; + +async function discover(): Promise { + if (discoveryCache) return discoveryCache; + if (discoveryPromise) return discoveryPromise; + + discoveryPromise = (async () => { + const url = `${OIDC_ISSUER_INTERNAL_URL}/.well-known/openid-configuration`; + const res = await fetch(url); + if (!res.ok) { + throw new Error( + `OIDC discovery failed: ${res.status} from ${url}. ` + + `Ensure OIDC_ISSUER_URL or KF_AUTH_URL points to a valid OIDC provider.`, + ); + } + const config = (await res.json()) as OIDCDiscovery; + discoveryCache = config; + return config; + })(); + + return discoveryPromise; +} + +/** Initialize OIDC — call at app startup to fail fast if provider is unreachable. */ +export async function initOidc(): Promise { + await discover(); +} + +/** + * Rewrite a discovered endpoint URL to use the internal host. + * Discovery may return URLs with the public host (BETTER_AUTH_URL), + * but server-to-server calls must use OIDC_ISSUER_INTERNAL_URL. + */ +function internalEndpoint(discoveredUrl: string): string { + const url = new URL(discoveredUrl); + const base = new URL(OIDC_ISSUER_INTERNAL_URL); + url.protocol = base.protocol; + url.host = base.host; + return url.toString(); +} + +// --- Symmetric encryption (AES-256-GCM) --- + +/** Derive a 32-byte key from the client secret for AES-256-GCM. */ +function deriveKey(): Buffer { + return crypto.createHash('sha256').update(OIDC_CLIENT_SECRET).digest(); +} + +/** Encrypt a JSON-serializable object → base64url token. */ +export function encryptPayload(data: object): string { + const key = deriveKey(); + const iv = crypto.randomBytes(12); + const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); + const plaintext = JSON.stringify(data); + const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); + const tag = cipher.getAuthTag(); + return Buffer.concat([iv, tag, encrypted]).toString('base64url'); +} + +/** Decrypt a base64url token → parsed object, or null on failure. */ +export function decryptPayload(token: string): T | null { + try { + const key = deriveKey(); + const buf = Buffer.from(token, 'base64url'); + if (buf.length < 29) return null; + const iv = buf.subarray(0, 12); + const tag = buf.subarray(12, 28); + const ciphertext = buf.subarray(28); + const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); + decipher.setAuthTag(tag); + const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + return JSON.parse(decrypted.toString('utf8')) as T; + } catch { + return null; + } +} + +// --- PKCE helpers --- + +export function generateCodeVerifier(): string { + return crypto.randomBytes(32).toString('base64url'); +} + +export function generateCodeChallenge(verifier: string): string { + return crypto.createHash('sha256').update(verifier).digest('base64url'); +} + +// --- OIDC Flows --- + +/** + * Build the URL to redirect the user to for authentication. + * Uses the discovered authorization_endpoint (browser-facing). + */ +export async function buildAuthorizeUrl( + state: string, + existingVerifier?: string, + context?: string, +): Promise<{ url: string; codeVerifier: string }> { + const config = await discover(); + const codeVerifier = existingVerifier ?? generateCodeVerifier(); + const codeChallenge = generateCodeChallenge(codeVerifier); + + // Use browser-facing URL for authorize endpoint + const authorizeUrl = new URL(config.authorization_endpoint); + const browserBase = new URL(OIDC_ISSUER_URL); + authorizeUrl.protocol = browserBase.protocol; + authorizeUrl.host = browserBase.host; + + const params = new URLSearchParams({ + client_id: OIDC_CLIENT_ID, + redirect_uri: REDIRECT_URI, + response_type: 'code', + scope: 'openid profile email', + state, + code_challenge: codeChallenge, + code_challenge_method: 'S256', + ...(context && { context }), + }); + + return { url: `${authorizeUrl.toString()}?${params}`, codeVerifier }; +} + +export interface TokenResponse { + access_token: string; + token_type: string; + expires_in: number; + id_token?: string; + refresh_token?: string; +} + +/** + * Exchange an authorization code for tokens (server-to-server). + */ +export async function exchangeCode(code: string, codeVerifier: string): Promise { + const config = await discover(); + const body = new URLSearchParams({ + grant_type: 'authorization_code', + code, + redirect_uri: REDIRECT_URI, + client_id: OIDC_CLIENT_ID, + client_secret: OIDC_CLIENT_SECRET, + code_verifier: codeVerifier, + }); + + const res = await fetch(internalEndpoint(config.token_endpoint), { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body, + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`Token exchange failed: ${res.status} ${text}`); + } + + return res.json() as Promise; +} + +// --- UserInfo --- + +export interface OIDCOrg { + id: string; + name: string; + slug: string; + type: 'personal' | 'shared'; + role: string; +} + +export interface OIDCUserInfo { + sub: string; + name?: string; + email?: string; + picture?: string; + given_name?: string; + family_name?: string; + [key: string]: unknown; +} + +export async function fetchUserInfo(accessToken: string): Promise { + const config = await discover(); + const res = await fetch(internalEndpoint(config.userinfo_endpoint), { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + if (!res.ok) { + throw new Error(`UserInfo failed: ${res.status}`); + } + + return res.json() as Promise; +} + +/** Extract org memberships from the userinfo response. */ +export function extractOrgs(userInfo: OIDCUserInfo): OIDCOrg[] { + const orgs = userInfo[OIDC_ORGS_CLAIM]; + if (Array.isArray(orgs)) return orgs as OIDCOrg[]; + return []; +} + +// --- Internal API (optional, for KF Auth specific features) --- + +const AUTH_INTERNAL_API_URL = + process.env.AUTH_INTERNAL_API_URL ?? + process.env.KF_AUTH_INTERNAL_URL ?? + process.env.KF_AUTH_URL ?? + OIDC_ISSUER_INTERNAL_URL; + +const AUTH_INTERNAL_API_KEY = + process.env.AUTH_INTERNAL_API_KEY ?? process.env.KF_INTERNAL_API_KEY ?? ''; + +/** Whether the internal API is configured and available. */ +export const hasInternalApi = Boolean(AUTH_INTERNAL_API_KEY); + +/** + * Fetch a user's orgs from the auth provider's internal API. + * Returns empty array if internal API is not configured. + */ +export async function fetchUserOrgs(userId: string): Promise { + if (!AUTH_INTERNAL_API_KEY) return []; + + const res = await fetch(`${AUTH_INTERNAL_API_URL}/api/internal/users/${userId}/orgs`, { + headers: { Authorization: `Bearer ${AUTH_INTERNAL_API_KEY}` }, + }); + + if (!res.ok) return []; + const data = (await res.json()) as { orgs?: OIDCOrg[] }; + return data.orgs ?? []; +} + +// --- Exports --- + +export { + OIDC_ISSUER_URL, + OIDC_ISSUER_INTERNAL_URL, + OIDC_CLIENT_ID, + OIDC_CLIENT_SECRET, + OIDC_ORGS_CLAIM, + APP_URL, + REDIRECT_URI, +}; + +// Legacy aliases +export { OIDC_ISSUER_URL as KF_AUTH_URL, OIDC_CLIENT_ID as KF_AUTH_CLIENT_ID }; diff --git a/server/server.ts b/server/server.ts index 1cd9c2fa2..489166a3d 100755 --- a/server/server.ts +++ b/server/server.ts @@ -339,6 +339,13 @@ app.use(appRouter); /* ------------ */ const port = env.PORT; export const startServer = async () => { + // Verify OIDC provider is reachable before accepting requests + const { initOidc } = await import('./kf/oidc.server.js'); + await initOidc().catch((err) => { + console.error('[OIDC] Failed to discover provider:', err.message); + process.exit(1); + }); + await sequelizeSyncPromise; return app.listen( port, From 9327556c90d19c8076ecfdd7353e1212b37608ff Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Wed, 20 May 2026 21:23:43 -0400 Subject: [PATCH 15/16] Handle oidc discovery more gracefully --- server/kf/oidc.server.ts | 8 ++++++-- server/server.ts | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/kf/oidc.server.ts b/server/kf/oidc.server.ts index 051b2b899..2acf1cff3 100644 --- a/server/kf/oidc.server.ts +++ b/server/kf/oidc.server.ts @@ -61,12 +61,16 @@ async function discover(): Promise { const config = (await res.json()) as OIDCDiscovery; discoveryCache = config; return config; - })(); + })().catch((err) => { + // Clear the cached promise so subsequent calls can retry + discoveryPromise = null; + throw err; + }); return discoveryPromise; } -/** Initialize OIDC — call at app startup to fail fast if provider is unreachable. */ +/** Pre-warm OIDC discovery cache. Non-fatal — discovery will be retried on demand. */ export async function initOidc(): Promise { await discover(); } diff --git a/server/server.ts b/server/server.ts index 489166a3d..a201775a9 100755 --- a/server/server.ts +++ b/server/server.ts @@ -339,11 +339,10 @@ app.use(appRouter); /* ------------ */ const port = env.PORT; export const startServer = async () => { - // Verify OIDC provider is reachable before accepting requests + // Pre-warm OIDC discovery (non-fatal — will retry on first auth request) const { initOidc } = await import('./kf/oidc.server.js'); await initOidc().catch((err) => { - console.error('[OIDC] Failed to discover provider:', err.message); - process.exit(1); + console.warn('[OIDC] Discovery failed at startup (will retry on demand):', err.message); }); await sequelizeSyncPromise; From d4c462683e0dfb32cc7b6f1b30dcfc302107af05 Mon Sep 17 00:00:00 2001 From: Travis Rich Date: Wed, 20 May 2026 22:00:08 -0400 Subject: [PATCH 16/16] Cleanup KF_ envs --- server/kf/api.ts | 14 ++++++------ server/kf/auth.ts | 4 ++-- server/kf/oidc.server.ts | 35 ++++++++++-------------------- server/routes/passwordReset.kf.tsx | 4 ++-- server/routes/signup.kf.tsx | 8 +++---- 5 files changed, 27 insertions(+), 38 deletions(-) diff --git a/server/kf/api.ts b/server/kf/api.ts index 12003317c..fa6ed2a4b 100644 --- a/server/kf/api.ts +++ b/server/kf/api.ts @@ -7,7 +7,7 @@ docker service logs auth_auth --tail 50 2>&1 | grep -i "error\|invalid\|authoriz * GET /auth/session-set — establish session on custom domains (via encrypted token) * POST /auth/logout — clear session + redirect to KF Auth logout * - * Internal service-to-service endpoints (KF_INTERNAL_API_KEY): + * Internal service-to-service endpoints (AUTH_INTERNAL_API_KEY): * POST /api/kf/profile-sync — receive profile updates from KF Auth * GET /api/kf/branding — return community branding for login page * GET /api/kf/summary — return community list for a KF org @@ -38,20 +38,20 @@ import { fetchUserInfo, fetchUserOrgs, generateCodeVerifier, - KF_AUTH_URL, + OIDC_ISSUER_URL, } from './auth'; // ── Helpers ────────────────────────────────────────────────────────── -const KF_INTERNAL_API_KEY = process.env.KF_INTERNAL_API_KEY; +const AUTH_INTERNAL_API_KEY = process.env.AUTH_INTERNAL_API_KEY; function requireInternalKey(req: any, res: any, next: () => void): void { - if (!KF_INTERNAL_API_KEY) { - res.status(500).json({ error: 'KF_INTERNAL_API_KEY not configured' }); + if (!AUTH_INTERNAL_API_KEY) { + res.status(500).json({ error: 'AUTH_INTERNAL_API_KEY not configured' }); return; } const auth = req.headers.authorization; - const expected = `Bearer ${KF_INTERNAL_API_KEY}`; + const expected = `Bearer ${AUTH_INTERNAL_API_KEY}`; // Use timing-safe comparison to prevent timing attacks on the API key if ( !auth || @@ -284,7 +284,7 @@ router.post('/auth/logout', (req: any, res: any) => { // Redirect to KF Auth's logout endpoint so the SSO session is also cleared const returnUrl = `${process.env.APP_URL || 'http://localhost:9876'}/`; return res.redirect( - `${KF_AUTH_URL}/api/auth/sign-out?callbackURL=${encodeURIComponent(returnUrl)}`, + `${OIDC_ISSUER_URL}/api/auth/sign-out?callbackURL=${encodeURIComponent(returnUrl)}`, ); }); }); diff --git a/server/kf/auth.ts b/server/kf/auth.ts index 7866033df..59be35e74 100644 --- a/server/kf/auth.ts +++ b/server/kf/auth.ts @@ -15,8 +15,8 @@ export { generateCodeChallenge, generateCodeVerifier, initOidc, - KF_AUTH_CLIENT_ID, - KF_AUTH_URL, + OIDC_CLIENT_ID, + OIDC_ISSUER_URL, type OIDCOrg as KFOrg, type OIDCUserInfo as KFUserInfo, REDIRECT_URI, diff --git a/server/kf/oidc.server.ts b/server/kf/oidc.server.ts index 2acf1cff3..734e47250 100644 --- a/server/kf/oidc.server.ts +++ b/server/kf/oidc.server.ts @@ -4,11 +4,11 @@ * Reads endpoints from the provider's .well-known/openid-configuration. * Works with any standards-compliant OIDC provider (KF Auth, Keycloak, Auth0, etc.). * - * Env vars (new canonical names with backward-compat fallbacks): - * OIDC_ISSUER_URL — browser-facing issuer URL (fallback: KF_AUTH_URL) - * OIDC_ISSUER_INTERNAL_URL — server-to-server URL for Docker (fallback: KF_AUTH_INTERNAL_URL, then OIDC_ISSUER_URL) - * OIDC_CLIENT_ID — OAuth client ID (fallback: KF_AUTH_CLIENT_ID) - * OIDC_CLIENT_SECRET — OAuth client secret (fallback: KF_AUTH_CLIENT_SECRET) + * Env vars: + * OIDC_ISSUER_URL — browser-facing issuer URL + * OIDC_ISSUER_INTERNAL_URL — server-to-server URL for Docker (falls back to OIDC_ISSUER_URL) + * OIDC_CLIENT_ID — OAuth client ID + * OIDC_CLIENT_SECRET — OAuth client secret * OIDC_ORGS_CLAIM — custom claim key for org memberships (default: https://knowledgefutures.org/orgs) */ @@ -16,16 +16,13 @@ import * as crypto from 'node:crypto'; // --- Config (with backward-compat fallbacks) --- -const OIDC_ISSUER_URL = - process.env.OIDC_ISSUER_URL ?? process.env.KF_AUTH_URL ?? 'http://localhost:3000'; +const OIDC_ISSUER_URL = process.env.OIDC_ISSUER_URL ?? 'http://localhost:3000'; -const OIDC_ISSUER_INTERNAL_URL = - process.env.OIDC_ISSUER_INTERNAL_URL ?? process.env.KF_AUTH_INTERNAL_URL ?? OIDC_ISSUER_URL; +const OIDC_ISSUER_INTERNAL_URL = process.env.OIDC_ISSUER_INTERNAL_URL ?? OIDC_ISSUER_URL; -const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID ?? process.env.KF_AUTH_CLIENT_ID ?? 'kf_pubpub'; +const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID ?? 'kf_pubpub'; -const OIDC_CLIENT_SECRET = - process.env.OIDC_CLIENT_SECRET ?? process.env.KF_AUTH_CLIENT_SECRET ?? ''; +const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET ?? ''; const OIDC_ORGS_CLAIM = process.env.OIDC_ORGS_CLAIM ?? 'https://knowledgefutures.org/orgs'; @@ -55,7 +52,7 @@ async function discover(): Promise { if (!res.ok) { throw new Error( `OIDC discovery failed: ${res.status} from ${url}. ` + - `Ensure OIDC_ISSUER_URL or KF_AUTH_URL points to a valid OIDC provider.`, + `Ensure OIDC_ISSUER_URL points to a valid OIDC provider.`, ); } const config = (await res.json()) as OIDCDiscovery; @@ -247,14 +244,9 @@ export function extractOrgs(userInfo: OIDCUserInfo): OIDCOrg[] { // --- Internal API (optional, for KF Auth specific features) --- -const AUTH_INTERNAL_API_URL = - process.env.AUTH_INTERNAL_API_URL ?? - process.env.KF_AUTH_INTERNAL_URL ?? - process.env.KF_AUTH_URL ?? - OIDC_ISSUER_INTERNAL_URL; +const AUTH_INTERNAL_API_URL = process.env.AUTH_INTERNAL_API_URL ?? OIDC_ISSUER_INTERNAL_URL; -const AUTH_INTERNAL_API_KEY = - process.env.AUTH_INTERNAL_API_KEY ?? process.env.KF_INTERNAL_API_KEY ?? ''; +const AUTH_INTERNAL_API_KEY = process.env.AUTH_INTERNAL_API_KEY ?? ''; /** Whether the internal API is configured and available. */ export const hasInternalApi = Boolean(AUTH_INTERNAL_API_KEY); @@ -286,6 +278,3 @@ export { APP_URL, REDIRECT_URI, }; - -// Legacy aliases -export { OIDC_ISSUER_URL as KF_AUTH_URL, OIDC_CLIENT_ID as KF_AUTH_CLIENT_ID }; diff --git a/server/routes/passwordReset.kf.tsx b/server/routes/passwordReset.kf.tsx index 6e4e4437e..c0be4dd17 100644 --- a/server/routes/passwordReset.kf.tsx +++ b/server/routes/passwordReset.kf.tsx @@ -7,11 +7,11 @@ import { Router } from 'express'; -import { KF_AUTH_URL } from 'server/kf/auth'; +import { OIDC_ISSUER_URL } from 'server/kf/auth'; export const router = Router(); router.get(['/password-reset', '/password-reset/:resetHash/:slug'], (req, res) => { // Old reset links won't work; redirect to KF Auth's password reset flow - return res.redirect(`${KF_AUTH_URL}/forgot-password`); + return res.redirect(`${OIDC_ISSUER_URL}/forgot-password`); }); diff --git a/server/routes/signup.kf.tsx b/server/routes/signup.kf.tsx index cc173f962..d20ce31bf 100644 --- a/server/routes/signup.kf.tsx +++ b/server/routes/signup.kf.tsx @@ -7,7 +7,7 @@ import { Router } from 'express'; -import { APP_URL, KF_AUTH_CLIENT_ID, KF_AUTH_URL } from 'server/kf/auth'; +import { APP_URL, OIDC_CLIENT_ID, OIDC_ISSUER_URL } from 'server/kf/auth'; export const router = Router(); @@ -15,15 +15,15 @@ router.get('/signup', (req, res) => { // Redirect to KF Auth's sign-up page, passing the PubPub client_id // so KF Auth shows PubPub-branded signup and redirects back after. const params = new URLSearchParams({ - client_id: KF_AUTH_CLIENT_ID, + client_id: OIDC_CLIENT_ID, redirect_uri: `${APP_URL}/auth/callback`, }); - return res.redirect(`${KF_AUTH_URL}/sign-up?${params}`); + return res.redirect(`${OIDC_ISSUER_URL}/sign-up?${params}`); }); // Also redirect the /user/create/:hash route (email verification step) // These links in old verification emails won't work after migration; // users who click them should be directed to sign up fresh via KF Auth. router.get('/user/create/:hash', (req, res) => { - return res.redirect(`${KF_AUTH_URL}/sign-up`); + return res.redirect(`${OIDC_ISSUER_URL}/sign-up`); });