Browse Source

Token type improvement

update-deps
Alex Mikhalev 7 years ago
parent
commit
187172e9e7
  1. 4
      client/state/Token.ts
  2. 5
      client/state/TokenStore.ts
  3. 1
      client/styles/app.scss
  4. 12
      common/TokenClaims.ts
  5. 4
      server/express/api/devices.ts
  6. 24
      server/express/authentication.ts
  7. 14
      server/sprinklersRpc/websocketServer.ts

4
client/state/Token.ts

@ -2,10 +2,10 @@ import { TokenClaims } from "@common/TokenClaims"; @@ -2,10 +2,10 @@ import { TokenClaims } from "@common/TokenClaims";
import * as jwt from "jsonwebtoken";
import { computed, createAtom, IAtom, observable } from "mobx";
export class Token {
export class Token<TClaims extends TokenClaims = TokenClaims> {
@observable token: string | null;
@computed get claims(): TokenClaims | null {
@computed get claims(): TClaims | null {
if (this.token == null) {
return null;
}

5
client/state/TokenStore.ts

@ -1,12 +1,13 @@ @@ -1,12 +1,13 @@
import { observable } from "mobx";
import { Token } from "@client/state/Token";
import { AccessToken, RefreshToken } from "@common/TokenClaims";
const LOCAL_STORAGE_KEY = "TokenStore";
export class TokenStore {
@observable accessToken: Token = new Token();
@observable refreshToken: Token = new Token();
@observable accessToken: Token<AccessToken> = new Token();
@observable refreshToken: Token<RefreshToken> = new Token();
clear() {
this.accessToken.token = null;

1
client/styles/app.scss

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
.app {
padding-top: 1em;
padding-bottom: 1em;
}
.flex-horizontal-space-between {

12
common/TokenClaims.ts

@ -3,8 +3,14 @@ export interface BaseClaims { @@ -3,8 +3,14 @@ export interface BaseClaims {
exp?: number;
}
export interface AccessOrRefreshToken extends BaseClaims {
type: "access" | "refresh";
export interface AccessToken extends BaseClaims {
type: "access";
aud: number;
name: string;
}
export interface RefreshToken extends BaseClaims {
type: "refresh";
aud: number;
name: string;
}
@ -13,4 +19,4 @@ export interface DeviceRegistrationToken extends BaseClaims { @@ -13,4 +19,4 @@ export interface DeviceRegistrationToken extends BaseClaims {
type: "device_reg";
}
export type TokenClaims = AccessOrRefreshToken | DeviceRegistrationToken;
export type TokenClaims = AccessToken | RefreshToken | DeviceRegistrationToken;

4
server/express/api/devices.ts

@ -4,7 +4,7 @@ import { serialize} from "serializr"; @@ -4,7 +4,7 @@ import { serialize} from "serializr";
import ApiError from "@common/ApiError";
import { ErrorCode } from "@common/ErrorCode";
import * as schema from "@common/sprinklersRpc/schema";
import { AccessOrRefreshToken } from "@common/TokenClaims";
import { AccessToken } from "@common/TokenClaims";
import { verifyAuthorization } from "@server/express/authentication";
import { ServerState } from "@server/state";
@ -12,7 +12,7 @@ export function devices(state: ServerState) { @@ -12,7 +12,7 @@ export function devices(state: ServerState) {
const router = PromiseRouter();
router.get("/:deviceId", verifyAuthorization(), async (req, res) => {
const token = req.token! as AccessOrRefreshToken;
const token = req.token!;
const userId = token.aud;
const deviceId = req.params.deviceId;
const userDevice = await state.database.sprinklersDevices

24
server/express/authentication.ts

@ -10,16 +10,14 @@ import { @@ -10,16 +10,14 @@ import {
TokenGrantRequest,
TokenGrantResponse,
} from "@common/httpApi";
import { TokenClaims } from "@common/TokenClaims";
import { AccessToken, DeviceRegistrationToken, RefreshToken, TokenClaims } from "@common/TokenClaims";
import { User } from "../entities";
import { ServerState } from "../state";
export { TokenClaims };
declare global {
namespace Express {
interface Request {
token?: TokenClaims;
token?: AccessToken;
}
}
}
@ -53,7 +51,9 @@ function signToken(claims: TokenClaims): Promise<string> { @@ -53,7 +51,9 @@ function signToken(claims: TokenClaims): Promise<string> {
});
}
export function verifyToken(token: string): Promise<TokenClaims> {
export function verifyToken<TClaims extends TokenClaims = TokenClaims>(
token: string, type?: TClaims["type"],
): Promise<TClaims> {
return new Promise((resolve, reject) => {
jwt.verify(token, JWT_SECRET, {
issuer: ISSUER,
@ -67,14 +67,18 @@ export function verifyToken(token: string): Promise<TokenClaims> { @@ -67,14 +67,18 @@ export function verifyToken(token: string): Promise<TokenClaims> {
reject(err);
}
} else {
resolve(decoded as any);
const claims: TokenClaims = decoded as any;
if (type != null && claims.type !== type) {
reject(new ApiError(`Expected a "${type} token, received a "${claims.type}" token`));
}
resolve(claims as TClaims);
}
});
});
}
function generateAccessToken(user: User, secret: string): Promise<string> {
const access_token_claims: TokenClaims = {
const access_token_claims: AccessToken = {
iss: ISSUER,
aud: user.id,
name: user.name,
@ -86,7 +90,7 @@ function generateAccessToken(user: User, secret: string): Promise<string> { @@ -86,7 +90,7 @@ function generateAccessToken(user: User, secret: string): Promise<string> {
}
function generateRefreshToken(user: User, secret: string): Promise<string> {
const refresh_token_claims: TokenClaims = {
const refresh_token_claims: RefreshToken = {
iss: ISSUER,
aud: user.id,
name: user.name,
@ -98,7 +102,7 @@ function generateRefreshToken(user: User, secret: string): Promise<string> { @@ -98,7 +102,7 @@ function generateRefreshToken(user: User, secret: string): Promise<string> {
}
function generateDeviceRegistrationToken(secret: string): Promise<string> {
const device_reg_token_claims: TokenClaims = {
const device_reg_token_claims: DeviceRegistrationToken = {
iss: ISSUER,
type: "device_reg",
};
@ -197,7 +201,7 @@ export function verifyAuthorization(options?: Partial<VerifyAuthorizationOpts>): @@ -197,7 +201,7 @@ export function verifyAuthorization(options?: Partial<VerifyAuthorizationOpts>):
}
const token = matches[1];
req.token = await verifyToken(token);
req.token = await verifyToken<AccessToken>(token, "access");
if (req.token.type !== opts.type) {
throw new ApiError(`Invalid token type "${req.token.type}", must be "${opts.type}"`,

14
server/sprinklersRpc/websocketServer.ts

@ -9,8 +9,9 @@ import * as deviceRequests from "@common/sprinklersRpc/deviceRequests"; @@ -9,8 +9,9 @@ import * as deviceRequests from "@common/sprinklersRpc/deviceRequests";
import * as schema from "@common/sprinklersRpc/schema";
import * as ws from "@common/sprinklersRpc/websocketData";
import { User } from "@server/entities";
import { TokenClaims, verifyToken } from "@server/express/authentication";
import { verifyToken } from "@server/express/authentication";
import { ServerState } from "@server/state";
import { AccessToken } from "@common/TokenClaims";
// tslint:disable:member-ordering
@ -78,19 +79,16 @@ export class WebSocketClient { @@ -78,19 +79,16 @@ export class WebSocketClient {
if (!data.accessToken) {
throw new ws.RpcError("no token specified", ErrorCode.BadRequest);
}
let decoded: TokenClaims;
let claims: AccessToken;
try {
decoded = await verifyToken(data.accessToken);
claims = await verifyToken<AccessToken>(data.accessToken, "access");
} catch (e) {
throw new ws.RpcError("invalid token", ErrorCode.BadToken, e);
}
if (decoded.type !== "access") {
throw new ws.RpcError("not an access token", ErrorCode.BadToken);
}
this.userId = decoded.aud;
this.userId = claims.aud;
this.user = await this.state.database.users.
findById(this.userId, { devices: true }) || null;
log.info({ userId: decoded.aud, name: decoded.name }, "authenticated websocket client");
log.info({ userId: claims.aud, name: claims.name }, "authenticated websocket client");
this.subscribeBrokerConnection();
return {
result: "success",

Loading…
Cancel
Save