first commit
This commit is contained in:
43
services/web/scripts/oauth/backfill_hashed_secrets.mjs
Normal file
43
services/web/scripts/oauth/backfill_hashed_secrets.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
db,
|
||||
READ_PREFERENCE_SECONDARY,
|
||||
} from '../../app/src/infrastructure/mongodb.js'
|
||||
import { hashSecret } from '../../modules/oauth2-server/app/src/SecretsHelper.js'
|
||||
|
||||
async function main() {
|
||||
console.log('Hashing client secrets...')
|
||||
await hashSecrets(db.oauthApplications, 'clientSecret')
|
||||
console.log('Hashing access tokens...')
|
||||
await hashSecrets(db.oauthAccessTokens, 'accessToken')
|
||||
console.log('Hashing refresh tokens...')
|
||||
await hashSecrets(db.oauthAccessTokens, 'refreshToken')
|
||||
console.log('Hashing authorization codes...')
|
||||
await hashSecrets(db.oauthAuthorizationCodes, 'authorizationCode')
|
||||
}
|
||||
|
||||
async function hashSecrets(collection, field) {
|
||||
const cursor = collection.find(
|
||||
{
|
||||
[field]: /^(?!v1\.)/,
|
||||
},
|
||||
{
|
||||
projection: { _id: 1, [field]: 1 },
|
||||
readPreference: READ_PREFERENCE_SECONDARY,
|
||||
}
|
||||
)
|
||||
let hashedCount = 0
|
||||
for await (const doc of cursor) {
|
||||
const hash = hashSecret(doc[field])
|
||||
await collection.updateOne({ _id: doc._id }, { $set: { [field]: hash } })
|
||||
hashedCount++
|
||||
}
|
||||
console.log(`${hashedCount} secrets hashed`)
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
96
services/web/scripts/oauth/create_token.mjs
Normal file
96
services/web/scripts/oauth/create_token.mjs
Normal file
@@ -0,0 +1,96 @@
|
||||
import minimist from 'minimist'
|
||||
import { db } from '../../app/src/infrastructure/mongodb.js'
|
||||
import { hashSecret } from '../../modules/oauth2-server/app/src/SecretsHelper.js'
|
||||
|
||||
async function main() {
|
||||
const opts = parseArgs()
|
||||
if (opts.accessToken == null) {
|
||||
console.error('Missing --token option')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (opts.refreshToken == null) {
|
||||
console.error('Missing --refresh-token option')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (opts.oauthApplication_id == null) {
|
||||
console.error('Missing --application-id option')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (opts.user_id == null) {
|
||||
console.error('Missing --user-id option')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (opts.scope == null) {
|
||||
console.error('Missing --scope option')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (opts.accessTokenExpiresAt == null) {
|
||||
console.error('Missing --expiry-date option')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
await insertToken(opts)
|
||||
}
|
||||
|
||||
async function insertToken(opts) {
|
||||
const token = {
|
||||
...opts,
|
||||
accessToken: hashSecret(opts.accessToken),
|
||||
refreshToken: hashSecret(opts.refreshToken),
|
||||
accessTokenExpiresAt: new Date(opts.accessTokenExpiresAt),
|
||||
createdAt: new Date(),
|
||||
}
|
||||
|
||||
await db.oauthAccessTokens.insertOne(token)
|
||||
}
|
||||
|
||||
function parseArgs() {
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
})
|
||||
if (args.help) {
|
||||
usage()
|
||||
process.exit(0)
|
||||
}
|
||||
if (args._.length !== 0) {
|
||||
usage()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return {
|
||||
accessToken: args.token,
|
||||
oauthApplication_id: args['application-id'],
|
||||
refreshToken: args['refresh-token'],
|
||||
user_id: args['user-id'],
|
||||
scope: args.scope,
|
||||
accessTokenExpiresAt: args['expiry-date'],
|
||||
}
|
||||
}
|
||||
|
||||
function usage() {
|
||||
console.error(`Usage: create_token.js [OPTS...]
|
||||
|
||||
Creates an OAuth access token
|
||||
|
||||
Options:
|
||||
--application-id ID for the OAuth application
|
||||
--user-id ID of the user this token belongs to
|
||||
--token Access token
|
||||
--refresh-token Refresh token
|
||||
--scope Accepted scope
|
||||
--expiry-date Token expiry date
|
||||
`)
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
129
services/web/scripts/oauth/register_client.mjs
Normal file
129
services/web/scripts/oauth/register_client.mjs
Normal file
@@ -0,0 +1,129 @@
|
||||
import minimist from 'minimist'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import { db } from '../../app/src/infrastructure/mongodb.js'
|
||||
import { hashSecret } from '../../modules/oauth2-server/app/src/SecretsHelper.js'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
async function main() {
|
||||
const opts = parseArgs()
|
||||
const application = await getApplication(opts.id)
|
||||
if (application == null) {
|
||||
console.log(
|
||||
`Application ${opts.id} is not registered. Creating a new configuration.`
|
||||
)
|
||||
if (opts.name == null) {
|
||||
console.error('Missing --name option')
|
||||
process.exit(1)
|
||||
}
|
||||
if (opts.secret == null) {
|
||||
console.error('Missing --secret option')
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
console.log(`Updating configuration for client: ${application.name}`)
|
||||
if (opts.mongoId != null) {
|
||||
console.error('Cannot change Mongo ID for an existing client')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
await upsertApplication(opts)
|
||||
}
|
||||
|
||||
async function getApplication(clientId) {
|
||||
return await db.oauthApplications.findOne({ id: clientId })
|
||||
}
|
||||
|
||||
async function upsertApplication(opts) {
|
||||
const key = { id: opts.id }
|
||||
const defaults = {}
|
||||
const updates = {}
|
||||
if (opts.name != null) {
|
||||
updates.name = opts.name
|
||||
}
|
||||
if (opts.secret != null) {
|
||||
updates.clientSecret = hashSecret(opts.secret)
|
||||
}
|
||||
if (opts.grants != null) {
|
||||
updates.grants = opts.grants
|
||||
} else {
|
||||
defaults.grants = []
|
||||
}
|
||||
if (opts.scopes != null) {
|
||||
updates.scopes = opts.scopes
|
||||
} else {
|
||||
defaults.scopes = []
|
||||
}
|
||||
if (opts.redirectUris != null) {
|
||||
updates.redirectUris = opts.redirectUris
|
||||
} else {
|
||||
defaults.redirectUris = []
|
||||
}
|
||||
if (opts.mongoId != null) {
|
||||
defaults._id = new ObjectId(opts.mongoId)
|
||||
}
|
||||
|
||||
await db.oauthApplications.updateOne(
|
||||
key,
|
||||
{
|
||||
$setOnInsert: { ...key, ...defaults },
|
||||
$set: updates,
|
||||
},
|
||||
{ upsert: true }
|
||||
)
|
||||
}
|
||||
|
||||
function parseArgs() {
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
})
|
||||
if (args.help) {
|
||||
usage()
|
||||
process.exit(0)
|
||||
}
|
||||
if (args._.length !== 1) {
|
||||
usage()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return {
|
||||
id: args._[0],
|
||||
mongoId: args['mongo-id'],
|
||||
name: args.name,
|
||||
secret: args.secret,
|
||||
scopes: toArray(args.scope),
|
||||
grants: toArray(args.grant),
|
||||
redirectUris: toArray(args['redirect-uri']),
|
||||
}
|
||||
}
|
||||
|
||||
function usage() {
|
||||
console.error(`Usage: register_client.js [OPTS...] CLIENT_ID
|
||||
|
||||
Creates or updates an OAuth client configuration
|
||||
|
||||
Options:
|
||||
--name Descriptive name for the OAuth client (required for creation)
|
||||
--secret Client secret (required for creation)
|
||||
--scope Accepted scope (can be given more than once)
|
||||
--grant Accepted grant type (can be given more than once)
|
||||
--redirect-uri Accepted redirect URI (can be given more than once)
|
||||
--mongo-id Mongo ID to use if the configuration is created (optional)
|
||||
`)
|
||||
}
|
||||
|
||||
function toArray(value) {
|
||||
if (value != null && !Array.isArray(value)) {
|
||||
return [value]
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
121
services/web/scripts/oauth/remove_client.mjs
Normal file
121
services/web/scripts/oauth/remove_client.mjs
Normal file
@@ -0,0 +1,121 @@
|
||||
import minimist from 'minimist'
|
||||
import {
|
||||
db,
|
||||
READ_PREFERENCE_SECONDARY,
|
||||
} from '../../app/src/infrastructure/mongodb.js'
|
||||
|
||||
async function main() {
|
||||
const opts = parseArgs()
|
||||
const application = await getApplication(opts.clientId)
|
||||
if (application == null) {
|
||||
console.error(`Client configuration not found: ${opts.clientId}`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (opts.commit) {
|
||||
console.log(
|
||||
`Preparing to remove OAuth client configuration: ${application.name}.`
|
||||
)
|
||||
|
||||
const deletedAccessTokens = await deleteAccessTokens(application._id)
|
||||
console.log(`Deleted ${deletedAccessTokens} access tokens`)
|
||||
|
||||
const deletedAuthorizationCodes = await deleteAuthorizationCodes(
|
||||
application._id
|
||||
)
|
||||
console.log(`Deleted ${deletedAuthorizationCodes} authorization codes`)
|
||||
|
||||
await deleteApplication(application._id)
|
||||
console.log('Deleted OAuth client configuration')
|
||||
} else {
|
||||
console.log(
|
||||
`Preparing to remove OAuth client configuration (dry run): ${application.name}.`
|
||||
)
|
||||
const accessTokenCount = await countAccessTokens(application._id)
|
||||
const authorizationCodeCount = await countAuthorizationCodes(
|
||||
application._id
|
||||
)
|
||||
console.log(
|
||||
`This would delete ${accessTokenCount} access tokens and ${authorizationCodeCount} authorization codes.`
|
||||
)
|
||||
console.log('This was a dry run. Rerun with --commit to proceed.')
|
||||
}
|
||||
}
|
||||
|
||||
async function getApplication(clientId) {
|
||||
return await db.oauthApplications.findOne({ id: clientId })
|
||||
}
|
||||
|
||||
async function countAccessTokens(applicationId) {
|
||||
return await db.oauthAccessTokens.count(
|
||||
{
|
||||
oauthApplication_id: applicationId,
|
||||
},
|
||||
{ readPreference: READ_PREFERENCE_SECONDARY }
|
||||
)
|
||||
}
|
||||
|
||||
async function countAuthorizationCodes(applicationId) {
|
||||
return await db.oauthAuthorizationCodes.count(
|
||||
{
|
||||
oauthApplication_id: applicationId,
|
||||
},
|
||||
{ readPreference: READ_PREFERENCE_SECONDARY }
|
||||
)
|
||||
}
|
||||
|
||||
async function deleteAccessTokens(applicationId) {
|
||||
const res = await db.oauthAccessTokens.deleteMany({
|
||||
oauthApplication_id: applicationId,
|
||||
})
|
||||
return res.deletedCount
|
||||
}
|
||||
|
||||
async function deleteAuthorizationCodes(applicationId) {
|
||||
const res = await db.oauthAuthorizationCodes.deleteMany({
|
||||
oauthApplication_id: applicationId,
|
||||
})
|
||||
return res.deletedCount
|
||||
}
|
||||
|
||||
async function deleteApplication(applicationId) {
|
||||
await db.oauthApplications.deleteOne({ _id: applicationId })
|
||||
}
|
||||
|
||||
function parseArgs() {
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['help', 'commit'],
|
||||
})
|
||||
if (args.help) {
|
||||
usage()
|
||||
process.exit(0)
|
||||
}
|
||||
if (args._.length !== 1) {
|
||||
usage()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return {
|
||||
clientId: args._[0],
|
||||
commit: args.commit,
|
||||
}
|
||||
}
|
||||
|
||||
function usage() {
|
||||
console.error(`Usage: remove_client.js [OPTS...] CLIENT_ID
|
||||
|
||||
Removes an OAuth client configuration and all associated tokens and
|
||||
authorization codes
|
||||
|
||||
Options:
|
||||
--commit Really delete the OAuth application (will do a dry run by default)
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
Reference in New Issue
Block a user