Skip to content

Commit

Permalink
feat: add tokens to auth ctrl, sign/save/issue (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssube committed Dec 10, 2018
1 parent 8866a7e commit 64423d0
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 11 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@types/graphql": "^14.0.3",
"@types/js-yaml": "~3.11",
"@types/jsonpath": "^0.2.0",
"@types/jsonwebtoken": "^8.3.0",
"@types/lodash": "~4.14",
"@types/mathjs": "~4.4",
"@types/mocha": "~5.2",
Expand Down Expand Up @@ -59,6 +60,7 @@
"ineeda": "^0.12.0-alpha.0",
"istanbul-instrumenter-loader": "~3.0",
"js-yaml": "~3.12",
"jsonwebtoken": "^8.4.0",
"lodash": "~4.17",
"mathjs": "~5.3.1",
"mocha": "~5.2",
Expand Down
82 changes: 73 additions & 9 deletions src/controller/AuthController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Controller, ControllerData, ControllerOptions } from './Controller';
export const NOUN_PERMISSION = 'permission';
export const NOUN_ROLE = 'role';
export const NOUN_SESSION = 'session';
export const NOUN_TOKEN = 'token';
export const NOUN_USER = 'user';

export type AuthControllerData = ControllerData;
Expand All @@ -29,7 +30,7 @@ export class AuthController extends BaseController<AuthControllerData> implement
protected userRepository: UserRepository;

constructor(options: AuthControllerOptions) {
super(options, [NOUN_PERMISSION, NOUN_ROLE, NOUN_SESSION, NOUN_USER]);
super(options, [NOUN_PERMISSION, NOUN_ROLE, NOUN_SESSION, NOUN_TOKEN, NOUN_USER]);

this.storage = options.storage;
this.roleRepository = this.storage.getRepository(Role);
Expand All @@ -45,6 +46,8 @@ export class AuthController extends BaseController<AuthControllerData> implement
return this.handleRole(cmd);
case NOUN_SESSION:
return this.handleSession(cmd);
case NOUN_TOKEN:
return this.handleToken(cmd);
case NOUN_USER:
return this.handleUser(cmd);
default:
Expand Down Expand Up @@ -76,25 +79,38 @@ export class AuthController extends BaseController<AuthControllerData> implement
}
}

public async handleUser(cmd: Command): Promise<void> {
public async handleSession(cmd: Command): Promise<void> {
switch (cmd.verb) {
case CommandVerb.Create:
return this.createUser(cmd);
return this.createSession(cmd);
case CommandVerb.Get:
return this.getUser(cmd);
case CommandVerb.Update:
return this.updateUser(cmd);
return this.getSession(cmd);
default:
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, `unsupported verb: ${cmd.verb}`));
}
}

public async handleSession(cmd: Command): Promise<void> {
public async handleToken(cmd: Command): Promise<void> {
switch (cmd.verb) {
case CommandVerb.Create:
return this.createSession(cmd);
return this.createToken(cmd);
case CommandVerb.Get:
return this.getSession(cmd);
return this.getToken(cmd);
case CommandVerb.List:
return this.listTokens(cmd);
default:
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, `unsupported verb: ${cmd.verb}`));
}
}

public async handleUser(cmd: Command): Promise<void> {
switch (cmd.verb) {
case CommandVerb.Create:
return this.createUser(cmd);
case CommandVerb.Get:
return this.getUser(cmd);
case CommandVerb.Update:
return this.updateUser(cmd);
default:
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, `unsupported verb: ${cmd.verb}`));
}
Expand Down Expand Up @@ -141,6 +157,54 @@ export class AuthController extends BaseController<AuthControllerData> implement
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, JSON.stringify(roles)));
}

public async createToken(cmd: Command): Promise<void> {
if (!cmd.context.user) {
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, 'must be logged in'));
return;
}

const grants = cmd.getOrDefault('grants', []);
const token = await this.tokenRepository.save(new Token({
audience: [],
createdAt: Date.now(),
data: {},
expiresAt: Date.now() + 6000,
grants,
issuer: this.id,
labels: {},
subject: cmd.context.uid,
user: cmd.context.user,
}));
const jwt = token.sign('test');

await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, JSON.stringify(token)));
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, jwt));
}

public async getToken(cmd: Command): Promise<void> {
if (!cmd.context.token) {
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, 'must provide token'));
return;
}

await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, JSON.stringify(cmd.context.token)));
}

public async listTokens(cmd: Command): Promise<void> {
if (!cmd.context.user) {
await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, 'must be logged in'));
return;
}

const tokens = this.tokenRepository.find({
where: {
user: cmd.context.user,
},
});

await this.bot.sendMessage(Message.reply(cmd.context, TYPE_TEXT, JSON.stringify(tokens)));
}

public async createUser(cmd: Command): Promise<void> {
const name = cmd.getHeadOrDefault('name', cmd.context.name);
const roleNames = cmd.getOrDefault('roles', []);
Expand Down
17 changes: 17 additions & 0 deletions src/entity/auth/Token.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { sign } from 'jsonwebtoken';
import { newTrie } from 'shiro-trie';
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';

Expand All @@ -10,6 +11,7 @@ export interface TokenOptions extends Session, DataEntityOptions<Array<string>>
audience: Array<string>;
createdAt: number;
expiresAt: number;
grants: Array<string>;
issuer: string;
subject: string;
}
Expand Down Expand Up @@ -82,6 +84,7 @@ export class Token extends DataEntity<Array<string>> implements TokenOptions {
this.createdAt = options.createdAt;
this.expiresAt = options.expiresAt;
this.issuer = options.issuer;
this.grants = Array.from(options.grants);
this.subject = options.subject;
}
}
Expand All @@ -96,6 +99,20 @@ export class Token extends DataEntity<Array<string>> implements TokenOptions {
return permissions.every((p) => trie.check(p));
}

/**
* Produce a JWT from this token.
*/
public sign(secret: string) {
return sign(this.toJSON(), secret, {
audience: this.audience,
expiresIn: this.expiresAt - this.createdAt,
issuer: this.issuer,
jwtid: this.id,
notBefore: this.createdAt,
subject: this.subject,
});
}

public toJSON(): object {
return {
id: this.id,
Expand Down
3 changes: 3 additions & 0 deletions src/migration/0001544317462-CreateToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class CreateToken0001544317462 implements MigrationInterface {
}, {
name: 'expiresAt',
type: 'int',
}, {
name: 'grants',
type: 'varchar',
}, {
name: 'issuer',
type: 'varchar',
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@
version "0.2.0"
resolved "https://registry.yarnpkg.com/@types/jsonpath/-/jsonpath-0.2.0.tgz#13c62db22a34d9c411364fac79fd374d63445aa1"

"@types/jsonwebtoken@*":
"@types/jsonwebtoken@*", "@types/jsonwebtoken@^8.3.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#1fe79489df97b49273401ac3c8019cbf1dae4578"
dependencies:
Expand Down Expand Up @@ -3333,7 +3333,7 @@ jsonpath@^1.0.0, jsonpath@~1.0:
static-eval "2.0.0"
underscore "1.7.0"

jsonwebtoken@^8.2.0:
jsonwebtoken@^8.2.0, jsonwebtoken@^8.4.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz#8757f7b4cb7440d86d5e2f3becefa70536c8e46a"
dependencies:
Expand Down

0 comments on commit 64423d0

Please sign in to comment.