Skip to content

Commit

Permalink
fix(auth): device code flow fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
awlayton committed Jul 27, 2024
1 parent 52b09ba commit b636419
Show file tree
Hide file tree
Showing 30 changed files with 259 additions and 217 deletions.
2 changes: 1 addition & 1 deletion oada/libs/lib-arangodb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"@types/deep-equal": "^1.0.4",
"@types/flat": "^5.0.5",
"@types/json-pointer": "^1.0.34",
"@types/node": "^20.14.11",
"@types/node": "^20.14.12",
"ava": "6.1.3",
"type-fest": "^4.23.0"
},
Expand Down
45 changes: 25 additions & 20 deletions oada/libs/lib-arangodb/src/libs/deviceCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ import { db as database } from '../db.js';
import { sanitizeResult } from '../util.js';

export interface DeviceCode {
/** @internal */
_id?: string;
/** @internal */
_key?: string;
deviceCode: string;
userCode: string;
}
Expand All @@ -40,8 +36,8 @@ export async function findByDeviceCode(
const cursor = await database.query<DeviceCode>(
aql`
FOR c IN ${deviceCodes}
FILTER c.deviceCode == ${deviceCode}
RETURN c`,
FILTER c.deviceCode == ${deviceCode}
RETURN c`,
);

const c = await cursor.next();
Expand All @@ -54,8 +50,8 @@ export async function findByUserCode(
const cursor = await database.query<DeviceCode>(
aql`
FOR c IN ${deviceCodes}
FILTER c.userCode == ${userCode}
RETURN c`,
FILTER c.userCode == ${userCode}
RETURN c`,
);

const c = await cursor.next();
Expand All @@ -65,19 +61,28 @@ export async function findByUserCode(
export async function save(
deviceCode: DeviceCode,
): Promise<DeviceCode | undefined> {
const { new: saved } = await deviceCodes.save(deviceCode);
const { new: saved } = await deviceCodes.save(deviceCode, { returnNew: true });
return saved;
}

export async function remove({
_id,
}: DeviceCode): Promise<DeviceCode | undefined> {
if (_id === undefined) {
throw new TypeError('Invalid device code');
}

try {
const { old } = await deviceCodes.remove(_id, { returnOld: true });
return old!;
} catch {}
export async function redeem(deviceCode: string) {
const cursor = await database.query<{ redeemed: boolean, code?: DeviceCode }>(
aql`
LET code = (
FOR c IN ${deviceCodes}
FILTER c.deviceCode == ${deviceCode}
RETURN c
)
LET clear = (
FOR c IN code
FILTER HAS(c, 'approved')
REMOVE c IN ${deviceCodes}
RETURN OLD
)
RETURN {
redeemed: LENGTH(clear) == 1,
code: FIRST(code),
}
`);
return (await cursor.next())!;
}
2 changes: 1 addition & 1 deletion oada/libs/lib-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"dotenv": "^16.4.5",
"json5": "^2.2.3",
"tslib": "2.6.3",
"yaml": "^2.4.5"
"yaml": "^2.5.0"
},
"devDependencies": {
"@types/convict": "^6.1.6",
Expand Down
2 changes: 1 addition & 1 deletion oada/libs/lib-kafka/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@ava/typescript": "^5.0.0",
"@types/convict": "^6.1.6",
"@types/debug": "^4.1.12",
"@types/node": "^20.14.11",
"@types/node": "^20.14.12",
"@types/uuid": "^10.0.0",
"ava": "6.1.3"
},
Expand Down
2 changes: 1 addition & 1 deletion oada/libs/lib-prom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"devDependencies": {
"@ava/typescript": "^5.0.0",
"@types/convict": "^6.1.6",
"@types/node": "^20.14.11",
"@types/node": "^20.14.12",
"@types/ws": "^8.5.11",
"ava": "6.1.3",
"fastify-plugin": "^4.5.1"
Expand Down
2 changes: 1 addition & 1 deletion oada/libs/lib-prom/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const { config, schema } = await libConfig({
host: {
doc: 'Bind host for exposing metrics endpoint',
format: String,
default: 'localhost',
default: '0.0.0.0',
env: 'PROM_HOST',
arg: 'prom-host',
},
Expand Down
2 changes: 1 addition & 1 deletion oada/libs/models/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"node": "20.2.0"
},
"devDependencies": {
"@types/node": "^20.14.11",
"@types/node": "^20.14.12",
"jose": "^5.6.3"
}
}
6 changes: 3 additions & 3 deletions oada/libs/pino-debug/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@
"dependencies": {
"cls-rtracer": "^2.6.3",
"is-interactive": "^2.0.0",
"pino": "^9.3.1",
"pino": "^9.3.2",
"pino-caller": "^3.4.0",
"pino-debug": "^2.0.0",
"pino-loki": "^2.3.0",
"pino-pretty": "^11.2.1",
"pino-pretty": "^11.2.2",
"tslib": "2.6.3"
},
"devDependencies": {
"@types/debug": "^4.1.12",
"@types/node": "^20.14.11"
"@types/node": "^20.14.12"
},
"peerDependencies": {
"debug": "*"
Expand Down
12 changes: 6 additions & 6 deletions oada/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@tsconfig/node20": "^20.1.4",
"@types/eslint": "^9.6.0",
"@types/mocha": "^10.0.7",
"@types/node": "^20.14.11",
"@types/node": "^20.14.12",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"@yarnpkg/sdks": "^3.1.3",
Expand All @@ -37,24 +37,24 @@
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-array-func": "^5.0.1",
"eslint-plugin-ava": "^15.0.1",
"eslint-plugin-escompat": "^3.8.1",
"eslint-plugin-escompat": "^3.11.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-github": "^5.0.1",
"eslint-plugin-i18n-text": "^1.0.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^17.9.0",
"eslint-plugin-n": "^17.10.1",
"eslint-plugin-no-constructor-bind": "^2.0.4",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-no-secrets": "^1.0.2",
"eslint-plugin-notice": "^1.0.0",
"eslint-plugin-optimize-regex": "^1.2.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-promise": "^7.0.0",
"eslint-plugin-regexp": "^2.6.0",
"eslint-plugin-security": "^3.0.1",
"eslint-plugin-sonarjs": "^1.0.4",
"eslint-plugin-unicorn": "^54.0.0",
"eslint-plugin-unicorn": "^55.0.0",
"get-port": "^7.1.0",
"prettier": "^3.3.3",
"tslib": "2.6.3",
Expand All @@ -64,7 +64,7 @@
"zx": "^8.1.4"
},
"dependencies": {
"pino-pretty": "^11.2.1"
"pino-pretty": "^11.2.2"
},
"resolutions": {
"@fastify/jwt": "^8.0.0",
Expand Down
6 changes: 3 additions & 3 deletions oada/services/auth/src/cli/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async function getClient(iss: string) {
} catch (error: unknown) {
if (error instanceof errors.OPError) {
// @ts-expect-error stuff
error.message = `${error.response.body.message}`;
error.message ??= `${error.response.body.message}`;
}

throw error;
Expand Down Expand Up @@ -97,13 +97,13 @@ export const get = command({
}),
},
// eslint-disable-next-line @typescript-eslint/no-shadow
async handler({ scope, iss, qr }) {
async handler({ scope = [], iss, qr }) {
const client = await getClient(
iss ? `${iss}` : `${config.get('oidc.issuer')}`,
);

const handle = await client.deviceAuthorization({
scope: scope?.join(' '),
scope: scope.join(' '),
});

console.group('User verification:');
Expand Down
28 changes: 9 additions & 19 deletions oada/services/auth/src/db/arango/deviceCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,22 @@ const trace = debug('arango:deviceCodes:trace');

export const findByDeviceCode = async function (deviceCode) {
trace('findByCode: searching for code %s', deviceCode);
const found = await deviceCodes.findByDeviceCode(deviceCode);
if (!found) {
return found;
}

const { _id, _key, ...c } = found;
return c;
return deviceCodes.findByDeviceCode(deviceCode);
} satisfies IDeviceCodes['findByDeviceCode'];

export const findByUserCode = async function (userCode) {
trace('findByCode: searching for code %s', userCode);
const found = await deviceCodes.findByUserCode(userCode);
if (!found) {
return found;
}

const { _id, _key, ...c } = found;
return c;
return deviceCodes.findByUserCode(userCode);
} satisfies IDeviceCodes['findByUserCode'];

export const save = async function <C extends DeviceCode>(deviceCode: C) {
return (await deviceCodes.save(deviceCode)) as C;
} satisfies IDeviceCodes['save'];

export const remove = async function (deviceCode) {
return deviceCodes.remove(deviceCode) as unknown as Promise<
DeviceCode | undefined
>;
} satisfies IDeviceCodes['remove'];
export const redeem = async function (deviceCode) {
const { redeemed, code } = await deviceCodes.redeem(deviceCode);
return {
redeemed,
code: code as DeviceCode,
}
} satisfies IDeviceCodes['redeem'];
4 changes: 2 additions & 2 deletions oada/services/auth/src/db/models/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import type { Except, Promisable } from 'type-fest';

import { Client } from '@oada/models/client';

import { getDataStores, tryDataStores } from './index.js';
import { type Store, getDataStores, tryDataStores } from './index.js';

export { Client } from '@oada/models/client';

export interface IClients {
export interface IClients extends Store {
findById(id: string): Promisable<Client | undefined>;
save(client: Except<Client, 'client_id'>): Promisable<void>;
}
Expand Down
4 changes: 2 additions & 2 deletions oada/services/auth/src/db/models/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import debug from 'debug';

import type { CodeID } from '@oada/lib-arangodb/dist/libs/codes.js';

import { getDataStores, tryDataStores } from './index.js';
import { type Store, getDataStores, tryDataStores } from './index.js';

const trace = debug('model-codes:trace');

export interface ICodes {
export interface ICodes extends Store {
findByCode(code: Code['code']): Promise<ICode | undefined>;
save(code: Code): Promise<void>;
}
Expand Down
23 changes: 13 additions & 10 deletions oada/services/auth/src/db/models/deviceCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ import base36 from 'random-id-base36';

import { destructure } from '@oada/models/decorators';

import { getDataStores, tryDataStores } from './index.js';
import { type Store, getDataStores, tryDataStores } from './index.js';

export interface IDeviceCodes {
export interface IDeviceCodes extends Store {
findByDeviceCode(
deviceCode: DeviceCode['deviceCode'],
): Promisable<Partial<DeviceCode> | undefined>;
findByUserCode(
userCode: DeviceCode['userCode'],
): Promisable<Partial<DeviceCode> | undefined>;
save<C extends DeviceCode>(code: C): Promisable<C>;
remove(code: DeviceCode): Promisable<DeviceCode | undefined>;
redeem(code: DeviceCode['deviceCode']): Promisable<{ redeemed: boolean, code?: DeviceCode }>;
}

const dataStores = await getDataStores<IDeviceCodes>(
Expand Down Expand Up @@ -63,7 +63,7 @@ class DeviceCode {
constructor(
rest: Partial<DeviceCode> = {},
readonly clientId: string,
readonly scope: readonly string[],
readonly scope: string,
/**
* Machine-readable code for client use
*/
Expand Down Expand Up @@ -117,20 +117,23 @@ export async function activate(
}
}

export async function redeem(clientId: string, deviceCode: DeviceCode) {
export async function redeem(clientId: string, deviceCode: DeviceCode['deviceCode']): Promise<{ redeemed: boolean, code?: DeviceCode }> {
async function redeemDeviceCode(dataStore: IDeviceCodes) {
const old = await dataStore.remove(deviceCode);
if (!old) {
const { redeemed, code } = await dataStore.redeem(deviceCode);
if (!code) {
return;
}

const code = new DeviceCode(old);
const out = new DeviceCode(code);
if (code.clientId !== clientId) {
throw new Error('Client does not match original client');
}

return code;
return {
redeemed,
code: out,
};
}

return tryDataStores(dataStores, redeemDeviceCode);
return (await tryDataStores(dataStores, redeemDeviceCode)) ?? { redeemed: false }
}
Loading

0 comments on commit b636419

Please sign in to comment.