Git Komut Rehberi & Terminal Asistanı
Sıfırdan Yeni Bir Depo Başlatın:
Mevcut Bir Git Deposu veya Klasörü Gönderin:
Hataları Geri Alma, İptal & Temizlik:
Son commit'i geri al (yapılan değişiklikleri korur):
git reset --soft HEAD~1
Yapılan yerel değişiklikleri iptal et (çalışma ağacını temizler):
git restore .
Takip edilmeyen gereksiz dosyaları sil:
git clean -fd
Değişiklikleri geçici sakla ve geri getir (stash):
git stash git stash pop
Dal (Branch) ve Sürüm Etiketleri:
Yeni dal aç ve geçiş yap:
git checkout -b yeni-ozellik git push -u origin yeni-ozellik
Sürüm etiketi (tag) oluştur ve gönder:
git tag -a v1.0.0 -m "Sürüm 1.0.0" git push origin --tags
426 lines
15 KiB
JavaScript
426 lines
15 KiB
JavaScript
const crypto = require('crypto');
|
||
const https = require('https');
|
||
const http = require('http');
|
||
|
||
let clientId = '';
|
||
let clientSecret = '';
|
||
let authBaseUrl = 'https://developer-console.yakhub.com.tr';
|
||
let customCallbackUrl = '';
|
||
|
||
function normalizeAuthUrl(urlStr) {
|
||
if (typeof urlStr === 'string' && urlStr.includes('auth.yakhub.com.tr')) {
|
||
return urlStr.replace('auth.yakhub.com.tr', 'developer-console.yakhub.com.tr');
|
||
}
|
||
return urlStr;
|
||
}
|
||
|
||
function postRequest(urlStr, data, maxRedirects = 5) {
|
||
return new Promise((resolve, reject) => {
|
||
if (maxRedirects <= 0) {
|
||
return reject(new Error('Too many redirects while calling YakNet auth server'));
|
||
}
|
||
const safeUrlStr = normalizeAuthUrl(urlStr);
|
||
const url = new URL(safeUrlStr);
|
||
const postData = typeof data === 'string' ? data : new URLSearchParams(data).toString();
|
||
const lib = url.protocol === 'https:' ? https : http;
|
||
|
||
const req = lib.request(
|
||
{
|
||
hostname: url.hostname,
|
||
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
||
path: url.pathname + (url.search || ''),
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
Accept: 'application/json',
|
||
'Content-Length': Buffer.byteLength(postData),
|
||
'User-Agent': 'YakTube-SSO/1.0'
|
||
}
|
||
},
|
||
res => {
|
||
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
|
||
const nextUrl = new URL(res.headers.location, safeUrlStr).toString();
|
||
return resolve(postRequest(nextUrl, data, maxRedirects - 1));
|
||
}
|
||
|
||
let body = '';
|
||
res.on('data', chunk => (body += chunk));
|
||
res.on('end', () => {
|
||
try {
|
||
resolve({ statusCode: res.statusCode, data: JSON.parse(body) });
|
||
} catch (e) {
|
||
resolve({ statusCode: res.statusCode, raw: body });
|
||
}
|
||
});
|
||
}
|
||
);
|
||
|
||
req.on('error', reject);
|
||
req.write(postData);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
function getRequest(urlStr, token, maxRedirects = 5) {
|
||
return new Promise((resolve, reject) => {
|
||
if (maxRedirects <= 0) {
|
||
return reject(new Error('Too many redirects while calling YakNet auth server'));
|
||
}
|
||
const safeUrlStr = normalizeAuthUrl(urlStr);
|
||
const url = new URL(safeUrlStr);
|
||
const lib = url.protocol === 'https:' ? https : http;
|
||
|
||
const req = lib.request(
|
||
{
|
||
hostname: url.hostname,
|
||
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
||
path: url.pathname + (url.search || ''),
|
||
method: 'GET',
|
||
headers: {
|
||
Authorization: 'Bearer ' + token,
|
||
Accept: 'application/json',
|
||
'User-Agent': 'YakTube-SSO/1.0'
|
||
}
|
||
},
|
||
res => {
|
||
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
|
||
const nextUrl = new URL(res.headers.location, safeUrlStr).toString();
|
||
return resolve(getRequest(nextUrl, token, maxRedirects - 1));
|
||
}
|
||
|
||
let body = '';
|
||
res.on('data', chunk => (body += chunk));
|
||
res.on('end', () => {
|
||
try {
|
||
resolve({ statusCode: res.statusCode, data: JSON.parse(body) });
|
||
} catch (e) {
|
||
resolve({ statusCode: res.statusCode, raw: body });
|
||
}
|
||
});
|
||
}
|
||
);
|
||
|
||
req.on('error', reject);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
async function register({ registerExternalAuth, registerSetting, settingsManager, getRouter, peertubeHelpers }) {
|
||
const logger = peertubeHelpers.logger;
|
||
const webserverUrl = (peertubeHelpers.config.getWebserverUrl() || '').replace(/\/+$/, '');
|
||
const isOfficialYakTube = webserverUrl.includes('yaktube.yakhub.com.tr');
|
||
// Standard PeerTube plugin router path uses short name (/plugins/auth-yaknet/router/auth-callback)
|
||
const defaultCallbackUrl = isOfficialYakTube
|
||
? `${webserverUrl}/plugins/peertube-plugin-auth-yaknet/router/auth-callback`
|
||
: `${webserverUrl}/plugins/auth-yaknet/router/auth-callback`;
|
||
|
||
registerSetting({
|
||
name: 'client-id',
|
||
label: 'YakNet Client ID',
|
||
type: 'input',
|
||
description: 'Your YakNet OAuth2 Client ID',
|
||
private: false,
|
||
default: ''
|
||
});
|
||
|
||
registerSetting({
|
||
name: 'client-secret',
|
||
label: 'YakNet Client Secret',
|
||
type: 'input-password',
|
||
description: 'Your YakNet OAuth2 Client Secret',
|
||
private: true,
|
||
default: ''
|
||
});
|
||
|
||
registerSetting({
|
||
name: 'auth-base-url',
|
||
label: 'YakNet Auth URL',
|
||
type: 'input',
|
||
description: 'YakNet SSO Server URL (Default: https://developer-console.yakhub.com.tr)',
|
||
private: false,
|
||
default: 'https://developer-console.yakhub.com.tr'
|
||
});
|
||
|
||
registerSetting({
|
||
name: 'callback-url',
|
||
label: 'OAuth Callback (Redirect) URL (Opsiyonel)',
|
||
type: 'input',
|
||
description: `Boş bırakılırsa otomatik olarak "${defaultCallbackUrl}" kullanılır. YakNet Geliştirici Konsolu'nda tanımladığınız Redirect URI adresini buraya yazabilirsiniz.`,
|
||
private: false,
|
||
default: ''
|
||
});
|
||
|
||
registerSetting({
|
||
name: 'auto-redirect-login',
|
||
label: "Giriş Sayfasında Doğrudan YakNet SSO'ya Yönlendir",
|
||
type: 'input-checkbox',
|
||
description:
|
||
'Aktif olduğunda, kullanıcılar giriş butonuna veya /login sayfasına gittiğinde standart PeerTube şifre formu yerine doğrudan YakNet SSO sunucusuna yönlendirilir.',
|
||
private: false,
|
||
default: true
|
||
});
|
||
|
||
let autoRedirectLogin = true;
|
||
|
||
function syncConfigFiles(enable) {
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
// Update in-memory PeerTube ServerConfig if accessible
|
||
try {
|
||
const srvCfg = peertubeHelpers.config.getServerConfig();
|
||
if (srvCfg && srvCfg.client && srvCfg.client.menu && srvCfg.client.menu.login) {
|
||
srvCfg.client.menu.login.redirectOnSingleExternalAuth = enable;
|
||
}
|
||
} catch (e) {}
|
||
|
||
// 1. Update local-production.json if present
|
||
const jsonCandidates = [
|
||
path.resolve(process.cwd(), 'config', 'local-production.json'),
|
||
path.resolve(__dirname, '..', '..', 'config', 'local-production.json'),
|
||
path.resolve(__dirname, '..', '..', '..', 'config', 'local-production.json'),
|
||
'c:/laragon/www/yaktube.yakhub.com.tr/config/local-production.json'
|
||
];
|
||
for (const jPath of jsonCandidates) {
|
||
if (fs.existsSync(jPath)) {
|
||
try {
|
||
const cfg = JSON.parse(fs.readFileSync(jPath, 'utf8'));
|
||
if (!cfg.menu) cfg.menu = {};
|
||
if (!cfg.menu.login) cfg.menu.login = {};
|
||
if (cfg.menu.login.redirect_on_single_external_auth !== enable) {
|
||
cfg.menu.login.redirect_on_single_external_auth = enable;
|
||
fs.writeFileSync(jPath, JSON.stringify(cfg, null, 2), 'utf8');
|
||
logger.info(`[YakNet SSO] Synced local-production.json redirect_on_single_external_auth = ${enable}`);
|
||
}
|
||
} catch (e) {
|
||
logger.warn('[YakNet SSO] Error updating local-production.json:', e.message);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 2. Update production.yaml if present
|
||
const yamlCandidates = [
|
||
path.resolve(process.cwd(), 'config', 'production.yaml'),
|
||
path.resolve(__dirname, '..', '..', 'config', 'production.yaml'),
|
||
path.resolve(__dirname, '..', '..', '..', 'config', 'production.yaml'),
|
||
'c:/laragon/www/yaktube.yakhub.com.tr/config/production.yaml'
|
||
];
|
||
for (const yPath of yamlCandidates) {
|
||
if (fs.existsSync(yPath)) {
|
||
try {
|
||
let yContent = fs.readFileSync(yPath, 'utf8');
|
||
const pattern = /(redirect_on_single_external_auth:\s*)(true|false)/;
|
||
if (pattern.test(yContent)) {
|
||
const updated = yContent.replace(pattern, `$1${enable}`);
|
||
if (updated !== yContent) {
|
||
fs.writeFileSync(yPath, updated, 'utf8');
|
||
logger.info(`[YakNet SSO] Synced production.yaml redirect_on_single_external_auth = ${enable}`);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
logger.warn('[YakNet SSO] Error updating production.yaml:', e.message);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
async function loadSettings() {
|
||
const cid = await settingsManager.getSetting('client-id');
|
||
const csec = await settingsManager.getSetting('client-secret');
|
||
const burl = await settingsManager.getSetting('auth-base-url');
|
||
const cburl = await settingsManager.getSetting('callback-url');
|
||
const autoRedir = await settingsManager.getSetting('auto-redirect-login');
|
||
if (cid) clientId = String(cid).trim();
|
||
if (csec) clientSecret = String(csec).trim();
|
||
if (burl && String(burl).trim()) {
|
||
authBaseUrl = normalizeAuthUrl(String(burl).trim().replace(/\/+$/, ''));
|
||
}
|
||
customCallbackUrl = cburl ? String(cburl).trim() : '';
|
||
if (autoRedir !== undefined && autoRedir !== null) {
|
||
autoRedirectLogin = autoRedir === true || autoRedir === 'true';
|
||
} else {
|
||
autoRedirectLogin = true;
|
||
}
|
||
syncConfigFiles(autoRedirectLogin);
|
||
}
|
||
await loadSettings();
|
||
settingsManager.onSettingsChange(loadSettings);
|
||
|
||
function getActiveCallbackUrl() {
|
||
return customCallbackUrl || defaultCallbackUrl;
|
||
}
|
||
|
||
async function dbQuery(sql, bindParams = []) {
|
||
if (!peertubeHelpers || !peertubeHelpers.database || typeof peertubeHelpers.database.query !== 'function') {
|
||
return [];
|
||
}
|
||
const res = await peertubeHelpers.database.query(sql, { bind: bindParams });
|
||
if (Array.isArray(res) && Array.isArray(res[0])) return res[0];
|
||
return Array.isArray(res) ? res : [];
|
||
}
|
||
|
||
const externalAuth = registerExternalAuth({
|
||
authName: 'yaknet',
|
||
authDisplayName: () => 'YakNet ile Giriş Yap',
|
||
onAuthRequest: (req, res) => {
|
||
const state = crypto.randomBytes(16).toString('hex');
|
||
const cbUrl = getActiveCallbackUrl();
|
||
const authUrl = `${normalizeAuthUrl(authBaseUrl)}/oauth/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(cbUrl)}&response_type=code&scope=&state=${state}`;
|
||
return res.redirect(authUrl);
|
||
}
|
||
});
|
||
|
||
const router = getRouter();
|
||
|
||
router.get('/status', (req, res) => {
|
||
return res.json({
|
||
autoRedirectLogin: autoRedirectLogin !== false,
|
||
callbackUrl: getActiveCallbackUrl()
|
||
});
|
||
});
|
||
|
||
router.get('/auth', (req, res) => {
|
||
const state = crypto.randomBytes(16).toString('hex');
|
||
const cbUrl = getActiveCallbackUrl();
|
||
const authUrl = `${normalizeAuthUrl(authBaseUrl)}/oauth/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(cbUrl)}&response_type=code&scope=&state=${state}`;
|
||
return res.redirect(authUrl);
|
||
});
|
||
|
||
async function handleOAuthCallback(req, res) {
|
||
await loadSettings();
|
||
const code = req.query.code;
|
||
const error = req.query.error;
|
||
|
||
if (error) {
|
||
logger.error('YakNet OAuth Error: ' + error);
|
||
return res.redirect('/login?externalAuthError=true&error=' + encodeURIComponent(error));
|
||
}
|
||
|
||
if (!code) {
|
||
return res.redirect('/login?externalAuthError=true');
|
||
}
|
||
|
||
try {
|
||
const targetAuthUrl = normalizeAuthUrl(authBaseUrl);
|
||
const cbUrl = getActiveCallbackUrl();
|
||
const tokenRes = await postRequest(`${targetAuthUrl}/oauth/token`, {
|
||
grant_type: 'authorization_code',
|
||
client_id: clientId,
|
||
client_secret: clientSecret,
|
||
redirect_uri: cbUrl,
|
||
code: code
|
||
});
|
||
|
||
if (!tokenRes.data || !tokenRes.data.access_token) {
|
||
logger.error('Failed to get access token from YakNet: ' + JSON.stringify(tokenRes));
|
||
return res.redirect('/login?externalAuthError=true');
|
||
}
|
||
|
||
const accessToken = tokenRes.data.access_token;
|
||
const userRes = await getRequest(`${targetAuthUrl}/api/user`, accessToken);
|
||
if (!userRes.data || !userRes.data.email) {
|
||
logger.error('Failed to get user profile from YakNet: ' + JSON.stringify(userRes));
|
||
return res.redirect('/login?externalAuthError=true');
|
||
}
|
||
|
||
const rawUser = userRes.data;
|
||
let username = (rawUser.username || rawUser.email.split('@')[0])
|
||
.toLowerCase()
|
||
.replace(/[^a-z0-9_.]/g, '_')
|
||
.substring(0, 50);
|
||
|
||
if (username.length < 3) {
|
||
username = username + '_yak';
|
||
}
|
||
|
||
const displayName = rawUser.name || rawUser.username || username;
|
||
const email = rawUser.email;
|
||
const role =
|
||
rawUser.is_admin === 1 ||
|
||
rawUser.is_admin === true ||
|
||
email === '[email protected]' ||
|
||
username === 'enesyakistir' ||
|
||
username === 'yaknet' ||
|
||
username === 'yaktube'
|
||
? 0
|
||
: 2;
|
||
|
||
// Pre-sync with PeerTube DB so existing accounts or username collisions never fail without bridge.js
|
||
try {
|
||
const existingByEmail = await dbQuery(
|
||
`SELECT id, username FROM "user" WHERE LOWER(email) = LOWER($1) LIMIT 1`,
|
||
[email]
|
||
);
|
||
if (existingByEmail.length > 0) {
|
||
username = existingByEmail[0].username;
|
||
await dbQuery(
|
||
`UPDATE "user" SET "pluginAuth" = 'peertube-plugin-auth-yaknet', "emailVerified" = true WHERE id = $1`,
|
||
[existingByEmail[0].id]
|
||
);
|
||
} else {
|
||
const existingByUsername = await dbQuery(`SELECT id FROM "user" WHERE LOWER(username) = LOWER($1) LIMIT 1`, [
|
||
username
|
||
]);
|
||
if (existingByUsername.length > 0) {
|
||
username = (username.substring(0, 42) + '_' + crypto.randomBytes(2).toString('hex')).toLowerCase();
|
||
}
|
||
}
|
||
} catch (dbErr) {
|
||
logger.warn('[YakNet SSO] Pre-sync DB warning: ' + dbErr.message);
|
||
}
|
||
|
||
logger.info(`YakNet Authenticated user: ${username} (${email})`);
|
||
|
||
externalAuth.userAuthenticated({
|
||
req,
|
||
res,
|
||
username,
|
||
email,
|
||
displayName,
|
||
role
|
||
});
|
||
|
||
setTimeout(async () => {
|
||
try {
|
||
await dbQuery(
|
||
`UPDATE "user" SET "emailVerified" = true, "pluginAuth" = 'peertube-plugin-auth-yaknet' WHERE LOWER(email) = LOWER($1)`,
|
||
[email]
|
||
);
|
||
} catch (e) {}
|
||
}, 1000);
|
||
} catch (err) {
|
||
logger.error('Error processing YakNet auth callback:', err);
|
||
return res.redirect('/login?externalAuthError=true');
|
||
}
|
||
}
|
||
|
||
router.get('/auth-callback', handleOAuthCallback);
|
||
|
||
// Also support legacy /plugins/peertube-plugin-auth-yaknet/router/auth-callback on any PeerTube server without bridge.js
|
||
try {
|
||
if (peertubeHelpers && peertubeHelpers.server && typeof peertubeHelpers.server.getHTTPServer === 'function') {
|
||
const httpServer = peertubeHelpers.server.getHTTPServer();
|
||
const listeners = httpServer ? httpServer.listeners('request') : [];
|
||
const expressApp = listeners && listeners[0];
|
||
if (expressApp && typeof expressApp.get === 'function') {
|
||
expressApp.get('/plugins/peertube-plugin-auth-yaknet/router/auth-callback', (req, res) => {
|
||
return handleOAuthCallback(req, res);
|
||
});
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
|
||
logger.info('YakNet SSO Plugin initialized with Callback URL: ' + getActiveCallbackUrl());
|
||
}
|
||
|
||
async function unregister() {
|
||
return true;
|
||
}
|
||
|
||
module.exports = {
|
||
register,
|
||
unregister
|
||
};
|