Skip to content

feat: add OAuth support for external applications #2251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ To add/remove DNS records you can now use `./sndev domains dns`. More on this [h
- [Login with Email](#login-with-email)
- [Login with Github](#login-with-github)
- [Login with Lightning](#login-with-lightning)
- [OAuth Applications](#oauth-applications)
- [Enabling web push notifications](#enabling-web-push-notifications)
- [Internals](#internals)
- [Stack](#stack)
Expand Down Expand Up @@ -525,3 +526,6 @@ If you found a vulnerability, we would greatly appreciate it if you contact us v

# License
[MIT](https://choosealicense.com/licenses/mit/)

## OAuth Applications
For details on how to create and use OAuth applications, refer to the [OAuth documentation](docs/dev/oauth.md).
4 changes: 2 additions & 2 deletions api/paidAction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ export class NonInvoiceablePeerError extends Error {

// we seperate the invoice creation into two functions because
// because if lnd is slow, it'll timeout the interactive tx
async function createSNInvoice (actionType, args, context) {
export async function createSNInvoice (actionType, args, context) {
const { me, lnd, cost, optimistic } = context
const action = paidActions[actionType]
const createLNDInvoice = optimistic ? createInvoice : createHodlInvoice
Expand All @@ -427,7 +427,7 @@ async function createSNInvoice (actionType, args, context) {
return { bolt11: invoice.request, preimage: invoice.secret }
}

async function createDbInvoice (actionType, args, context) {
export async function createDbInvoice (actionType, args, context) {
const { me, models, tx, cost, optimistic, actionId, invoiceArgs, paymentAttempt, predecessorId } = context
const { bolt11, wrappedBolt11, preimage, wallet, maxFee } = invoiceArgs

Expand Down
10 changes: 10 additions & 0 deletions api/resolvers/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ export default {
const { id, live } = await models.snl.findFirst()
await models.snl.update({ where: { id }, data: { live: !live } })
return !live
},
approveOAuthApplication: async (parent, { id }, { models, me }) => {
if (!me || !SN_ADMIN_IDS.includes(me.id)) {
throw new Error('not an admin')
}
const app = await models.oAuthApplication.update({
where: { id: Number(id) },
data: { approved: true }
})
return app
}
}
}
45 changes: 44 additions & 1 deletion api/resolvers/wallet.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
getInvoice as getInvoiceFromLnd, deletePayment, getPayment,
getInvoice as getInvoiceFromLnd, deletePayment, getPayment, createInvoice,
parsePaymentRequest
} from 'ln-service'
import crypto, { timingSafeEqual } from 'crypto'
Expand Down Expand Up @@ -474,6 +474,49 @@ const resolvers = {
__resolveType: invoiceOrDirect => invoiceOrDirect.__resolveType
},
Mutation: {
createInvoice: async (parent, { amount, expireMins, hodlInvoice, description, hash, hmac }, { me, models, lnd }) => {
if (!me) {
throw new GqlAuthenticationError()
}

if (hodlInvoice && (!hash || !hmac)) {
throw new GqlInputError('hash and hmac are required for hodl invoices')
}

if (hodlInvoice && hash && hmac) {
verifyHmac(hash, hmac)
}

const descriptionHash = hodlInvoice ? crypto.createHash('sha256').update(description).digest('hex') : undefined
const msats = amount * 1000
try {
const invoice = await createInvoice({
description: hodlInvoice ? undefined : description,
description_hash: descriptionHash,
expires_at: datePivot(new Date(), { minutes: expireMins || 1440 }),
mtokens: msats,
lnd,
is_including_private_channels: true
})

const inv = await models.invoice.create({
data: {
hash: invoice.id,
bolt11: invoice.request,
expiresAt: invoice.expires_at,
msatsRequested: msats,
userId: me.id,
description,
private: !!hodlInvoice
}
})

return inv
} catch (error) {
console.log(error)
throw new Error('Failed to create invoice')
}
},
createWithdrawl: createWithdrawal,
sendToLnAddr,
cancelInvoice: async (parent, { hash, hmac, userCancel }, { me, models, lnd, boss }) => {
Expand Down
23 changes: 20 additions & 3 deletions api/ssrApollo.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,10 @@ function oneDayReferral (request, { me }) {
* @param opts.notFound function that tests data to determine if 404
* @param opts.authRequired boolean that determines if auth is required
*/
export function getGetServerSideProps (
{ query: queryOrFunc, variables: varsOrFunc, notFound, authRequired }) {
return async function ({ req, res, query: params }) {
export function getGetServerSideProps (options, pageGetServerSideProps) {
const { query: queryOrFunc, variables: varsOrFunc, notFound, authRequired } = options
return async function (context) {
const { req, res, query: params } = context
const { nodata, ...realParams } = params
// we want to use client-side cache
if (nodata) return { props: { } }
Expand Down Expand Up @@ -211,11 +212,27 @@ export function getGetServerSideProps (
}
}

let pageProps = {}
if (pageGetServerSideProps) {
const pageResult = await pageGetServerSideProps(context)

if (pageResult.notFound) {
return { notFound: true }
}

if (pageResult.redirect) {
return { redirect: pageResult.redirect }
}

pageProps = pageResult.props
}

oneDayReferral(req, { me })

return {
props: {
...props,
...pageProps,
me,
price,
blockHeight,
Expand Down
1 change: 1 addition & 0 deletions api/typeDefs/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export default gql`

extend type Mutation {
onAirToggle: Boolean!
approveOAuthApplication(id: ID!): OAuthApplication!
}
`
5 changes: 4 additions & 1 deletion api/typeDefs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import chainFee from './chainFee'
import paidAction from './paidAction'
import vault from './vault'

import oauth from './oauth'

const common = gql`
type Query {
_: Boolean
Expand All @@ -36,7 +38,8 @@ const common = gql`
scalar JSONObject
scalar Date
scalar Limit
scalar BigInt
`

export default [common, user, item, itemForward, message, wallet, lnurl, notifications, invite,
sub, upload, growth, rewards, referrals, price, admin, blockHeight, chainFee, paidAction, vault]
sub, upload, growth, rewards, referrals, price, admin, blockHeight, chainFee, paidAction, vault, oauth]
76 changes: 76 additions & 0 deletions api/typeDefs/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { gql } from 'graphql-tag'

export default gql`
type OAuthApplication {
id: ID!
createdAt: Date!
updatedAt: Date!
name: String!
description: String
homepageUrl: String
privacyPolicyUrl: String
termsOfServiceUrl: String
clientId: String!
clientSecretHash: String
redirectUris: [String!]!
scopes: [String!]!
logoUrl: String
userId: ID!
approved: Boolean!
suspended: Boolean!
suspendedReason: String
rateLimitRpm: Int
rateLimitDaily: Int
isConfidential: Boolean!
pkceRequired: Boolean!
}

type OAuthWalletTransaction {
id: ID!
createdAt: Date!
updatedAt: Date!
userId: ID!
applicationId: ID!
accessTokenId: ID!
bolt11: String!
amountMsats: BigInt!
description: String
metadata: JSONObject
status: String!
approved: Boolean
approvedAt: Date
expiresAt: Date!
invoiceId: ID
withdrawalId: ID
}

extend type Query {
oAuthApplications: [OAuthApplication!]!
oAuthApplication(id: ID!): OAuthApplication
}

extend type Mutation {
createOAuthApplication(
name: String!
description: String
homepageUrl: String
privacyPolicyUrl: String
termsOfServiceUrl: String
redirectUris: [String!]!
scopes: [String!]!
logoUrl: String
): OAuthApplication!
updateOAuthApplication(
id: ID!
name: String
description: String
homepageUrl: String
privacyPolicyUrl: String
termsOfServiceUrl: String
redirectUris: [String!]
scopes: [String!]
logoUrl: String
): OAuthApplication!
deleteOAuthApplication(id: ID!): OAuthApplication
}
`
1 change: 1 addition & 0 deletions api/typeDefs/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const typeDefs = `
}

extend type Mutation {
createInvoice(amount: Int!, expireMins: Int, hodlInvoice: Boolean, description: String, hash: String, hmac: String): Invoice!
createWithdrawl(invoice: String!, maxFee: Int!): Withdrawl!
sendToLnAddr(addr: String!, amount: Int!, maxFee: Int!, comment: String, identifier: Boolean, name: String, email: String): Withdrawl!
cancelInvoice(hash: String!, hmac: String, userCancel: Boolean): Invoice!
Expand Down
Loading
Loading