Compare commits
3 Commits
contributi
...
token-last
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7da0146d3e | ||
|
|
f3bbcbde08 | ||
|
|
0559fe8649 |
@@ -53,6 +53,7 @@
|
|||||||
- @xen-orchestra/fs patch
|
- @xen-orchestra/fs patch
|
||||||
- @xen-orchestra/mixins minor
|
- @xen-orchestra/mixins minor
|
||||||
- @xen-orchestra/xapi minor
|
- @xen-orchestra/xapi minor
|
||||||
|
- xo-cli minor
|
||||||
- xo-server minor
|
- xo-server minor
|
||||||
- xo-server-backup-reports minor
|
- xo-server-backup-reports minor
|
||||||
- xo-server-netbox patch
|
- xo-server-netbox patch
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import humanFormat from 'human-format'
|
|||||||
import identity from 'lodash/identity.js'
|
import identity from 'lodash/identity.js'
|
||||||
import isObject from 'lodash/isObject.js'
|
import isObject from 'lodash/isObject.js'
|
||||||
import micromatch from 'micromatch'
|
import micromatch from 'micromatch'
|
||||||
|
import os from 'os'
|
||||||
import pairs from 'lodash/toPairs.js'
|
import pairs from 'lodash/toPairs.js'
|
||||||
import pick from 'lodash/pick.js'
|
import pick from 'lodash/pick.js'
|
||||||
import prettyMs from 'pretty-ms'
|
import prettyMs from 'pretty-ms'
|
||||||
@@ -47,7 +48,7 @@ async function connect() {
|
|||||||
return xo
|
return xo
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseRegisterArgs(args, tokenDescription, acceptToken = false) {
|
async function parseRegisterArgs(args, tokenDescription, client, acceptToken = false) {
|
||||||
const {
|
const {
|
||||||
allowUnauthorized,
|
allowUnauthorized,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
@@ -84,21 +85,21 @@ async function parseRegisterArgs(args, tokenDescription, acceptToken = false) {
|
|||||||
pw(resolve)
|
pw(resolve)
|
||||||
}),
|
}),
|
||||||
] = opts
|
] = opts
|
||||||
result.token = await _createToken({ ...result, description: tokenDescription, email, password })
|
result.token = await _createToken({ ...result, client, description: tokenDescription, email, password })
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _createToken({ allowUnauthorized, description, email, expiresIn, password, url }) {
|
async function _createToken({ allowUnauthorized, client, description, email, expiresIn, password, url }) {
|
||||||
const xo = new Xo({ rejectUnauthorized: !allowUnauthorized, url })
|
const xo = new Xo({ rejectUnauthorized: !allowUnauthorized, url })
|
||||||
await xo.open()
|
await xo.open()
|
||||||
try {
|
try {
|
||||||
await xo.signIn({ email, password })
|
await xo.signIn({ email, password })
|
||||||
console.warn('Successfully logged with', xo.user.email)
|
console.warn('Successfully logged with', xo.user.email)
|
||||||
|
|
||||||
return await xo.call('token.create', { description, expiresIn }).catch(error => {
|
return await xo.call('token.create', { client, description, expiresIn }).catch(error => {
|
||||||
// if invalid parameter error, retry without description for backward compatibility
|
// if invalid parameter error, retry without client and description for backward compatibility
|
||||||
if (error.code === 10) {
|
if (error.code === 10) {
|
||||||
return xo.call('token.create', { expiresIn })
|
return xo.call('token.create', { expiresIn })
|
||||||
}
|
}
|
||||||
@@ -219,6 +220,8 @@ function wrap(val) {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
|
const PACKAGE_JSON = JSON.parse(readFileSync(new URL('package.json', import.meta.url)))
|
||||||
|
|
||||||
const help = wrap(
|
const help = wrap(
|
||||||
(function (pkg) {
|
(function (pkg) {
|
||||||
return `Usage:
|
return `Usage:
|
||||||
@@ -355,7 +358,7 @@ $name v$version`.replace(/<([^>]+)>|\$(\w+)/g, function (_, arg, key) {
|
|||||||
|
|
||||||
return pkg[key]
|
return pkg[key]
|
||||||
})
|
})
|
||||||
})(JSON.parse(readFileSync(new URL('package.json', import.meta.url))))
|
})(PACKAGE_JSON)
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -422,9 +425,18 @@ async function createToken(args) {
|
|||||||
COMMANDS.createToken = createToken
|
COMMANDS.createToken = createToken
|
||||||
|
|
||||||
async function register(args) {
|
async function register(args) {
|
||||||
const opts = await parseRegisterArgs(args, 'xo-cli --register', true)
|
let { clientId } = await config.load()
|
||||||
|
if (clientId === undefined) {
|
||||||
|
clientId = Math.random().toString(36).slice(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, version } = PACKAGE_JSON
|
||||||
|
const label = `${name}@${version} - ${os.hostname()} - ${os.type()} ${os.machine()}`
|
||||||
|
|
||||||
|
const opts = await parseRegisterArgs(args, label, { id: clientId }, true)
|
||||||
await config.set({
|
await config.set({
|
||||||
allowUnauthorized: opts.allowUnauthorized,
|
allowUnauthorized: opts.allowUnauthorized,
|
||||||
|
clientId,
|
||||||
server: opts.url,
|
server: opts.url,
|
||||||
token: opts.token,
|
token: opts.token,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// TODO: Prevent token connections from creating tokens.
|
// TODO: Prevent token connections from creating tokens.
|
||||||
// TODO: Token permission.
|
// TODO: Token permission.
|
||||||
export async function create({ description, expiresIn }) {
|
export async function create({ client, description, expiresIn }) {
|
||||||
return (
|
return (
|
||||||
await this.createAuthenticationToken({
|
await this.createAuthenticationToken({
|
||||||
|
client,
|
||||||
description,
|
description,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
userId: this.apiContext.user.id,
|
userId: this.apiContext.user.id,
|
||||||
@@ -17,6 +18,15 @@ create.params = {
|
|||||||
optional: true,
|
optional: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
|
client: {
|
||||||
|
description:
|
||||||
|
'client this authentication token belongs to, if a previous token exists, it will be updated and returned',
|
||||||
|
optional: true,
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { description: 'unique identifier of this client', type: 'string' },
|
||||||
|
},
|
||||||
|
},
|
||||||
expiresIn: {
|
expiresIn: {
|
||||||
optional: true,
|
optional: true,
|
||||||
type: ['number', 'string'],
|
type: ['number', 'string'],
|
||||||
|
|||||||
@@ -3,7 +3,25 @@ import Collection from '../collection/redis.mjs'
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export class Tokens extends Collection {
|
export class Tokens extends Collection {
|
||||||
|
_serialize(token) {
|
||||||
|
const { client, lastUse } = token
|
||||||
|
if (client !== undefined) {
|
||||||
|
const { id, ...rest } = client
|
||||||
|
token.client_id = id
|
||||||
|
token.client = JSON.stringify(rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_unserialize(token) {
|
_unserialize(token) {
|
||||||
|
const { client, client_id } = token
|
||||||
|
if (client !== undefined) {
|
||||||
|
token.client = {
|
||||||
|
...JSON.parse(client),
|
||||||
|
id: client_id,
|
||||||
|
}
|
||||||
|
delete token.client_id
|
||||||
|
}
|
||||||
|
|
||||||
if (token.created_at !== undefined) {
|
if (token.created_at !== undefined) {
|
||||||
token.created_at = +token.created_at
|
token.created_at = +token.created_at
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,13 +49,23 @@ export default class {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Token authentication provider.
|
// Token authentication provider.
|
||||||
this.registerAuthenticationProvider(async ({ token: tokenId }) => {
|
this.registerAuthenticationProvider(async ({ token: tokenId }, { ip } = {}) => {
|
||||||
if (!tokenId) {
|
if (!tokenId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = await app.getAuthenticationToken(tokenId)
|
const token = await app.getAuthenticationToken(tokenId)
|
||||||
|
|
||||||
|
this._tokens.update({
|
||||||
|
...token,
|
||||||
|
|
||||||
|
lastUse: {
|
||||||
|
ip,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return { expiration: token.expiration, userId: token.user_id }
|
return { expiration: token.expiration, userId: token.user_id }
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
})
|
})
|
||||||
@@ -79,7 +89,7 @@ export default class {
|
|||||||
const tokensDb = (this._tokens = new Tokens({
|
const tokensDb = (this._tokens = new Tokens({
|
||||||
connection: app._redis,
|
connection: app._redis,
|
||||||
namespace: 'token',
|
namespace: 'token',
|
||||||
indexes: ['user_id'],
|
indexes: ['client_id', 'user_id'],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
app.addConfigManager(
|
app.addConfigManager(
|
||||||
@@ -180,7 +190,7 @@ export default class {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
async createAuthenticationToken({ description, expiresIn, userId }) {
|
async createAuthenticationToken({ client, description, expiresIn, userId }) {
|
||||||
let duration = this._defaultTokenValidity
|
let duration = this._defaultTokenValidity
|
||||||
if (expiresIn !== undefined) {
|
if (expiresIn !== undefined) {
|
||||||
duration = parseDuration(expiresIn)
|
duration = parseDuration(expiresIn)
|
||||||
@@ -191,8 +201,27 @@ export default class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tokens = this._tokens
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
|
const clientId = client?.id
|
||||||
|
if (clientId !== undefined) {
|
||||||
|
const token = await tokens.first({ client_id: clientId, user_id: userId })
|
||||||
|
if (token !== undefined) {
|
||||||
|
if (token.expiration > now) {
|
||||||
|
token.description = description
|
||||||
|
token.expiration = now + duration
|
||||||
|
tokens.update(token)::ignoreErrors()
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.remove(token.id)::ignoreErrors()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const token = {
|
const token = {
|
||||||
|
client,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
description,
|
description,
|
||||||
id: await generateToken(),
|
id: await generateToken(),
|
||||||
|
|||||||
Reference in New Issue
Block a user