2024-10-02 16:53:07 +03:00
import express from 'express' ;
import cors from 'cors' ;
import crypto from 'crypto' ;
import Parser from 'rss-parser' ;
import { parseFavicon } from 'parse-favicon' ;
import generateRSAKeypair from 'generate-rsa-keypair' ;
import config from '../config.js' ;
2018-10-15 07:18:10 +03:00
2024-10-02 16:53:07 +03:00
const router = express . Router ( ) ;
const oauth = config . OAUTH ;
export default router ;
router . get ( '/request-token' , cors ( ) , async ( req , res ) => {
2021-10-12 19:12:03 +03:00
if ( ! oauth ) {
return res . status ( 501 ) . json ( { message : ` OAuth is not enabled on this server. ` } ) ;
}
else if ( ! oauth . client _id || ! oauth . client _secret || ! oauth . redirect _uri ) {
return res . status ( 501 ) . json ( { message : ` OAuth is misconfigured on this server. Please contact the admin at ${ contactEmail } and let them know. ` } ) ;
}
else if ( ! req . query . code ) {
return res . status ( 400 ) . json ( { message : ` Request is missing the required 'code' parameter. ` } ) ;
}
let params = req . query ;
params . client _id = oauth . client _id ;
params . client _secret = oauth . client _secret ;
params . redirect _uri = oauth . redirect _uri ;
params . grant _type = 'authorization_code' ;
2024-10-02 16:53:07 +03:00
let response = await fetch ( ` https:// ${ oauth . domain } ${ oauth . token _path } ` , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
} ,
body : new URLSearchParams ( params )
2021-10-12 19:12:03 +03:00
} ) ;
2024-10-02 16:53:07 +03:00
let body = await response . json ( ) ;
if ( body . access _token ) {
return res . json ( { access _token : body . access _token , domain : oauth . domain } ) ;
} else {
return res . status ( 401 ) . json ( body ) ;
}
2021-10-12 19:12:03 +03:00
} ) ;
// if oauth is enabled, this function checks to see if we've been sent an access token and validates it with the server
// otherwise we simply skip verification
function isAuthenticated ( req , res , next ) {
if ( oauth ) {
2024-10-02 16:53:07 +03:00
fetch ( ` https:// ${ oauth . domain } ${ oauth . token _verification _path } ` , {
2021-10-12 19:12:03 +03:00
headers : {
'Authorization' : ` Bearer ${ req . query . token } `
}
2024-10-02 16:53:07 +03:00
} ) . then ( response => {
if ( response . status === 200 ) {
return next ( ) ;
} else {
2021-10-12 19:12:03 +03:00
res . redirect ( '/' ) ;
}
} ) ;
2024-10-02 16:53:07 +03:00
} else {
2021-10-12 19:12:03 +03:00
return next ( ) ;
}
}
router . get ( '/convert' , isAuthenticated , function ( req , res ) {
2018-10-15 07:18:10 +03:00
let db = req . app . get ( 'db' ) ;
let username = req . query . username ;
let feed = req . query . feed ;
// reject if username is invalid
if ( username . match ( /^[a-zA-Z0-9_]+$/ ) === null ) {
return res . status ( 400 ) . json ( 'Invalid username! Only alphanumerics and underscore (_) allowed.' ) ;
}
// check to see if feed exists
let result = db . prepare ( 'select * from feeds where feed = ? or username = ?' ) . get ( feed , username ) ;
// see if we already have an entry for this feed
if ( result ) {
// return feed
res . status ( 200 ) . json ( result ) ;
}
else if ( feed && username ) {
// validate the RSS
let parser = new Parser ( ) ;
parser . parseURL ( feed , function ( err , feedData ) {
if ( err ) {
2019-02-22 22:32:07 +02:00
if ( err . message === 'Status code 400' ) {
err . message = ` That doesn't look like a valid RSS feed. Check <a href=" ${ feed } ">the URL you provided</a> in a feed validator. You can <a href="https://validator.w3.org/feed/check.cgi?url= ${ feed } " target="_blank">click here</a> to pop up a test immediately. `
}
2018-10-15 07:18:10 +03:00
res . status ( 400 ) . json ( { err : err . message } ) ;
}
else {
2024-10-02 16:53:07 +03:00
console . log ( feedData ) ;
2018-10-15 07:18:10 +03:00
res . status ( 200 ) . json ( feedData ) ;
let displayName = feedData . title ;
2018-11-18 01:54:57 +02:00
let description = feedData . description ;
2018-10-15 07:18:10 +03:00
let account = username ;
// create new user
let db = req . app . get ( 'db' ) ;
let domain = req . app . get ( 'domain' ) ;
// create keypair
var pair = generateRSAKeypair ( ) ;
2018-10-15 21:45:32 +03:00
getImage ( feed , feedData , imageUrl => {
2018-11-18 01:54:57 +02:00
let actorRecord = createActor ( account , domain , pair . public , displayName , imageUrl , description ) ;
2018-10-15 21:45:32 +03:00
let webfingerRecord = createWebfinger ( account , domain ) ;
const apikey = crypto . randomBytes ( 16 ) . toString ( 'hex' ) ;
2024-10-02 16:53:07 +03:00
let result = db . prepare ( 'insert or replace into accounts(name, actor, apikey, pubkey, privkey, webfinger) values(?, ?, ?, ?, ?, ?)' ) . run ( ` ${ account } @ ${ domain } ` , JSON . stringify ( actorRecord ) , apikey , pair . public , pair . private , JSON . stringify ( webfingerRecord ) ) ;
2018-10-15 21:45:32 +03:00
let content = JSON . stringify ( feedData ) ;
2024-10-02 16:53:07 +03:00
result = db . prepare ( 'insert or replace into feeds(feed, username, content) values(?, ?, ?)' ) . run ( feed , username , content ) ;
2018-10-15 21:45:32 +03:00
} ) ;
2018-10-15 07:18:10 +03:00
}
} ) ;
}
else {
res . status ( 404 ) . json ( { msg : 'unknown error' } ) ;
}
} ) ;
2018-10-15 21:45:32 +03:00
function getImage ( feed , feedData , cb ) {
let imageUrl = null ;
// if image exists set image
if ( feedData . image && feedData . image . url ) {
imageUrl = feedData . image . url ;
return cb ( imageUrl ) ;
}
// otherwise parse the HTML for the favicon
else {
let favUrl = new URL ( feed ) ;
2024-10-02 16:53:07 +03:00
fetch ( favUrl . origin ) . then ( response => response . body ) . then ( body => {
const result = parseFavicon ( body , { baseURI : favUrl . origin } ) ;
if ( result && result . length ) {
return cb ( result [ 0 ] . url ) ;
}
else {
return cb ( null ) ;
}
2018-10-15 21:45:32 +03:00
} ) ;
}
}
2018-11-18 01:54:57 +02:00
function createActor ( name , domain , pubkey , displayName , imageUrl , description ) {
2018-10-15 07:18:10 +03:00
displayName = displayName || name ;
let actor = {
'@context' : [
'https://www.w3.org/ns/activitystreams' ,
'https://w3id.org/security/v1'
] ,
'id' : ` https:// ${ domain } /u/ ${ name } ` ,
2019-05-18 15:00:04 +03:00
'type' : 'Service' ,
2018-10-15 07:18:10 +03:00
'preferredUsername' : ` ${ name } ` ,
'inbox' : ` https:// ${ domain } /api/inbox ` ,
2019-03-11 06:02:12 +02:00
'followers' : ` https:// ${ domain } /u/ ${ name } /followers ` ,
2018-10-15 07:18:10 +03:00
'name' : displayName ,
'publicKey' : {
'id' : ` https:// ${ domain } /u/ ${ name } #main-key ` ,
'owner' : ` https:// ${ domain } /u/ ${ name } ` ,
'publicKeyPem' : pubkey
}
} ;
if ( imageUrl ) {
actor . icon = {
'type' : 'Image' ,
'mediaType' : 'image/png' ,
'url' : imageUrl ,
} ;
}
2018-11-18 01:54:57 +02:00
if ( description ) {
actor . summary = ` <p> ${ description } </p> ` ;
}
2018-10-15 07:18:10 +03:00
return actor ;
}
function createWebfinger ( name , domain ) {
return {
'subject' : ` acct: ${ name } @ ${ domain } ` ,
'links' : [
{
'rel' : 'self' ,
'type' : 'application/activity+json' ,
'href' : ` https:// ${ domain } /u/ ${ name } `
}
]
} ;
2024-10-02 16:53:07 +03:00
}