Token type improvement
This commit is contained in:
parent
c6c98d36a0
commit
187172e9e7
@ -2,10 +2,10 @@ import { TokenClaims } from "@common/TokenClaims";
|
|||||||
import * as jwt from "jsonwebtoken";
|
import * as jwt from "jsonwebtoken";
|
||||||
import { computed, createAtom, IAtom, observable } from "mobx";
|
import { computed, createAtom, IAtom, observable } from "mobx";
|
||||||
|
|
||||||
export class Token {
|
export class Token<TClaims extends TokenClaims = TokenClaims> {
|
||||||
@observable token: string | null;
|
@observable token: string | null;
|
||||||
|
|
||||||
@computed get claims(): TokenClaims | null {
|
@computed get claims(): TClaims | null {
|
||||||
if (this.token == null) {
|
if (this.token == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
|
|
||||||
import { Token } from "@client/state/Token";
|
import { Token } from "@client/state/Token";
|
||||||
|
import { AccessToken, RefreshToken } from "@common/TokenClaims";
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = "TokenStore";
|
const LOCAL_STORAGE_KEY = "TokenStore";
|
||||||
|
|
||||||
export class TokenStore {
|
export class TokenStore {
|
||||||
@observable accessToken: Token = new Token();
|
@observable accessToken: Token<AccessToken> = new Token();
|
||||||
@observable refreshToken: Token = new Token();
|
@observable refreshToken: Token<RefreshToken> = new Token();
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.accessToken.token = null;
|
this.accessToken.token = null;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
.app {
|
.app {
|
||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-horizontal-space-between {
|
.flex-horizontal-space-between {
|
||||||
|
@ -3,8 +3,14 @@ export interface BaseClaims {
|
|||||||
exp?: number;
|
exp?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessOrRefreshToken extends BaseClaims {
|
export interface AccessToken extends BaseClaims {
|
||||||
type: "access" | "refresh";
|
type: "access";
|
||||||
|
aud: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefreshToken extends BaseClaims {
|
||||||
|
type: "refresh";
|
||||||
aud: number;
|
aud: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
@ -13,4 +19,4 @@ export interface DeviceRegistrationToken extends BaseClaims {
|
|||||||
type: "device_reg";
|
type: "device_reg";
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TokenClaims = AccessOrRefreshToken | DeviceRegistrationToken;
|
export type TokenClaims = AccessToken | RefreshToken | DeviceRegistrationToken;
|
||||||
|
@ -4,7 +4,7 @@ import { serialize} from "serializr";
|
|||||||
import ApiError from "@common/ApiError";
|
import ApiError from "@common/ApiError";
|
||||||
import { ErrorCode } from "@common/ErrorCode";
|
import { ErrorCode } from "@common/ErrorCode";
|
||||||
import * as schema from "@common/sprinklersRpc/schema";
|
import * as schema from "@common/sprinklersRpc/schema";
|
||||||
import { AccessOrRefreshToken } from "@common/TokenClaims";
|
import { AccessToken } from "@common/TokenClaims";
|
||||||
import { verifyAuthorization } from "@server/express/authentication";
|
import { verifyAuthorization } from "@server/express/authentication";
|
||||||
import { ServerState } from "@server/state";
|
import { ServerState } from "@server/state";
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ export function devices(state: ServerState) {
|
|||||||
const router = PromiseRouter();
|
const router = PromiseRouter();
|
||||||
|
|
||||||
router.get("/:deviceId", verifyAuthorization(), async (req, res) => {
|
router.get("/:deviceId", verifyAuthorization(), async (req, res) => {
|
||||||
const token = req.token! as AccessOrRefreshToken;
|
const token = req.token!;
|
||||||
const userId = token.aud;
|
const userId = token.aud;
|
||||||
const deviceId = req.params.deviceId;
|
const deviceId = req.params.deviceId;
|
||||||
const userDevice = await state.database.sprinklersDevices
|
const userDevice = await state.database.sprinklersDevices
|
||||||
|
@ -10,16 +10,14 @@ import {
|
|||||||
TokenGrantRequest,
|
TokenGrantRequest,
|
||||||
TokenGrantResponse,
|
TokenGrantResponse,
|
||||||
} from "@common/httpApi";
|
} from "@common/httpApi";
|
||||||
import { TokenClaims } from "@common/TokenClaims";
|
import { AccessToken, DeviceRegistrationToken, RefreshToken, TokenClaims } from "@common/TokenClaims";
|
||||||
import { User } from "../entities";
|
import { User } from "../entities";
|
||||||
import { ServerState } from "../state";
|
import { ServerState } from "../state";
|
||||||
|
|
||||||
export { TokenClaims };
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Request {
|
interface Request {
|
||||||
token?: TokenClaims;
|
token?: AccessToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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) => {
|
return new Promise((resolve, reject) => {
|
||||||
jwt.verify(token, JWT_SECRET, {
|
jwt.verify(token, JWT_SECRET, {
|
||||||
issuer: ISSUER,
|
issuer: ISSUER,
|
||||||
@ -67,14 +67,18 @@ export function verifyToken(token: string): Promise<TokenClaims> {
|
|||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
} else {
|
} 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> {
|
function generateAccessToken(user: User, secret: string): Promise<string> {
|
||||||
const access_token_claims: TokenClaims = {
|
const access_token_claims: AccessToken = {
|
||||||
iss: ISSUER,
|
iss: ISSUER,
|
||||||
aud: user.id,
|
aud: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
@ -86,7 +90,7 @@ function generateAccessToken(user: User, secret: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateRefreshToken(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,
|
iss: ISSUER,
|
||||||
aud: user.id,
|
aud: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
@ -98,7 +102,7 @@ function generateRefreshToken(user: User, secret: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateDeviceRegistrationToken(secret: string): Promise<string> {
|
function generateDeviceRegistrationToken(secret: string): Promise<string> {
|
||||||
const device_reg_token_claims: TokenClaims = {
|
const device_reg_token_claims: DeviceRegistrationToken = {
|
||||||
iss: ISSUER,
|
iss: ISSUER,
|
||||||
type: "device_reg",
|
type: "device_reg",
|
||||||
};
|
};
|
||||||
@ -197,7 +201,7 @@ export function verifyAuthorization(options?: Partial<VerifyAuthorizationOpts>):
|
|||||||
}
|
}
|
||||||
const token = matches[1];
|
const token = matches[1];
|
||||||
|
|
||||||
req.token = await verifyToken(token);
|
req.token = await verifyToken<AccessToken>(token, "access");
|
||||||
|
|
||||||
if (req.token.type !== opts.type) {
|
if (req.token.type !== opts.type) {
|
||||||
throw new ApiError(`Invalid token type "${req.token.type}", must be "${opts.type}"`,
|
throw new ApiError(`Invalid token type "${req.token.type}", must be "${opts.type}"`,
|
||||||
|
@ -9,8 +9,9 @@ import * as deviceRequests from "@common/sprinklersRpc/deviceRequests";
|
|||||||
import * as schema from "@common/sprinklersRpc/schema";
|
import * as schema from "@common/sprinklersRpc/schema";
|
||||||
import * as ws from "@common/sprinklersRpc/websocketData";
|
import * as ws from "@common/sprinklersRpc/websocketData";
|
||||||
import { User } from "@server/entities";
|
import { User } from "@server/entities";
|
||||||
import { TokenClaims, verifyToken } from "@server/express/authentication";
|
import { verifyToken } from "@server/express/authentication";
|
||||||
import { ServerState } from "@server/state";
|
import { ServerState } from "@server/state";
|
||||||
|
import { AccessToken } from "@common/TokenClaims";
|
||||||
|
|
||||||
// tslint:disable:member-ordering
|
// tslint:disable:member-ordering
|
||||||
|
|
||||||
@ -78,19 +79,16 @@ export class WebSocketClient {
|
|||||||
if (!data.accessToken) {
|
if (!data.accessToken) {
|
||||||
throw new ws.RpcError("no token specified", ErrorCode.BadRequest);
|
throw new ws.RpcError("no token specified", ErrorCode.BadRequest);
|
||||||
}
|
}
|
||||||
let decoded: TokenClaims;
|
let claims: AccessToken;
|
||||||
try {
|
try {
|
||||||
decoded = await verifyToken(data.accessToken);
|
claims = await verifyToken<AccessToken>(data.accessToken, "access");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ws.RpcError("invalid token", ErrorCode.BadToken, e);
|
throw new ws.RpcError("invalid token", ErrorCode.BadToken, e);
|
||||||
}
|
}
|
||||||
if (decoded.type !== "access") {
|
this.userId = claims.aud;
|
||||||
throw new ws.RpcError("not an access token", ErrorCode.BadToken);
|
|
||||||
}
|
|
||||||
this.userId = decoded.aud;
|
|
||||||
this.user = await this.state.database.users.
|
this.user = await this.state.database.users.
|
||||||
findById(this.userId, { devices: true }) || null;
|
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();
|
this.subscribeBrokerConnection();
|
||||||
return {
|
return {
|
||||||
result: "success",
|
result: "success",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user