Redis - The client is closed - caching

I want to save a token in the redis cache after user sign-in in my app.
The cache config.service.ts file:
#Injectable()
export class CacheConfigService {
constructor(private configService: ConfigService) {}
get url(): string {
console.log(this.configService.get<string>('redis.url'));
return this.configService.get<string>('redis.url') as string;
}
}
The cacheModule file:
#Module({
imports: [
CacheModule.register<RedisClientOptions>({
// #ts-ignore
store: createRedisStore,
socket: {
host: 'localhost',
port: 6379,
},
isGlobal: true,
// url: 'redis://' + process.env.REDIS_HOST + ':' + process.env.REDIS_PORT,
}),
],
})
The user signin method:
emailPasswordSignInPOST: async (input: any) => {
if (
originalImplementation.emailPasswordSignInPOST === undefined
) {
throw Error('Should never come here');
}
let response: any =
await originalImplementation.emailPasswordSignInPOST(input);
const formFields: any = input.formFields;
const inputObject: any = {};
for (let index = 0; index < formFields.length; index++) {
const element = formFields[index];
inputObject[element.id] = element.value;
}
const { email } = inputObject;
const user = await this.userService.findOneByEmail(email);
const id = user?.id;
const payload = { email, id };
const secret = 'mysecret';
const token = jwt.sign(payload, secret, {
expiresIn: '2h',
});
response.token = token;
const cacheKey = 'Token';
await this.redisClient.set(cacheKey, token, {
EX: 60 * 60 * 24,
});
return response;
},
Please note that this is a different module.
When I send signin request from postman than it logs this error in the console: "Error: The client is closed"
I ran the app with the command DEBUG=* npm run start:dev to see the logs about redis but there is no log about redis.

Related

koa js backend is showing error - DB not connected -how to fix this issue

I am also trying different network connections it returns the same error. Please help I am stuck last 15 days in this error. Oh! last one thing my laptop IP is dynamically changing so what can I do know.
this is my mongoose connecter
const mongoose = require('mongoose')
const connection = () =>{
const str = 'mongodb://localhost:27017/524'
mongoose.connect(str , () =>{
console.log("Connection is successfull")
} )
}
module.exports = {connection }
this is server js
const koa = require('koa')
const cors = require('koa-cors')
const bodyParser = require('koa-parser')
const json = require('koa-json')
const {connection} = require('./dal')
const userRouter = require('./Router/userRouter')
const app = new koa()
const PORT = 8000
app.use(cors())
app.use(bodyParser())
app.use(json())
app.use(userRouter.routes()).use(userRouter.allowedMethods())
app.listen(PORT, ()=>{
console.log(`Server is running on port ${PORT}`)
connection();
})
this is modle class
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const UserSchema = new Schema ({
name:{
type:String,
required:true
},
email:{
type:String,
required:true,
unique:true
},
password:{
type:String,
required:true
},
role:{
type: Number,
default: 0
}
},{
timestamps:true
})
const User = mongoose.model('User', UserSchema)
module.exports = User
this is login route
const KoaRouter = require('koa-router')
const { register, login ,getAll } = require('../API/userAPI')
const userRouter = new KoaRouter({prefix: '/user'})
userRouter.post('/register', register)
userRouter.post('/login', login)
userRouter.get ('/get' , getAll)
module.exports = userRouter;
this is my contraller
const UserModel = require('../models/user.model')
const bcrypt = require('bcrypt')
const register = async (ctx) => {
try{
const user = ctx.request.body
const {name, email, password, role} = user
if (!name || !email || !password){
return (ctx.body = { message: "fill" });
}
else{
const exist = await UserModel.findOne({email})
if(exist){
return (ctx.body = { message: "User already exists" });
}else{
const salt = await bcrypt.genSalt();
const hashpassword = await bcrypt.hash(password, salt)
const newUser = new UserModel({
name,
email,
password : hashpassword,
role
})
await newUser.save()
return (ctx.body = { message: "User Registered" });
}
}
}catch(err){
console.log(err.message)
return (ctx.body = { message: err.message });
}
}
const login = async (ctx) => {
try{
const {email, password} = ctx.request.body
const user = await UserModel.findOne({email})
if (!user){
return ( ctx.body = {message:"User does not exist"})
}
else {
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
return (ctx.body = { message:"Wrong Password"})
}else{
return (ctx.body = { user})
}
}
}catch(err){
return (ctx.body= {err})
}
}
const getAll = async (ctx) => {
try{
const users = await UserModel.find()
return (ctx.body = users)
}catch(err){
return (ctx.body= {err})
}
}
module.exports = {register, login ,getAll}
.
how to fix this.any ideas.Can any body guide me with this scenario.

Post Error with Apollo Client While Creating a New User

I am running into a POST http://localhost:4000/ 400 (Bad Request) Error.
I am trying to create a new user with the following frontend.
const REGISTER_USER = gql`
mutation Mutation(
$createUser: CreateUserInput!
) {
createUser(createUserInput: $createUserInput){
email
name
token
password
}
}
`
const Register = () => {
const context = useContext(AuthContext)
let navigate = useNavigate()
const [errors, setErrors] = useState([])
function registerUserCallback() {
console.log("Callback hit")
registerUser()
}
const {onChange, onSubmit, values} = useForm(registerUserCallback, {
name: '',
email: '',
password:'',
confirmPassword: '',
})
const [registerUser, {loading}] = useMutation(REGISTER_USER, {
update(proxy, {data: {registerUser: userData}}) {
context.login(userData)
navigate('/Dashboard')
},
onError({graphQLErrors}) {
setErrors(graphQLErrors)
console.log("Error: " + graphQLErrors)
console.log(graphQLErrors)
},
variables: {createUserInput: values}
})
However, the grapQLErrors is not even being console.logged for some reason. When I run the Mutation via Apollo Studio it works. Any information would be great!
Edit: Network Tab Screenshot:
Adding Code for my httpLink:
import { ApolloClient, InMemoryCache, createHttpLink } from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
const httpLink = createHttpLink({
uri: 'http://localhost:4000'
})
const authLink = setContext((_, {headers}) => {
return {
headers: {
...headers,
authorization: localStorage.getItem('token') || ""
}
}
})
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Edit: createUser Mutation seems to be the issue. This is the Network response error: ["GraphQLError: Unknown argument "createUserInput" on field "Mutation.createUser".","
#Mutation((returns) => User)
async createUser(#Arg('data') data:CreateUserInput, #Ctx() ctx: Context) {
const oldUser = await ctx.prisma.user.findFirst({ where: { email: data.email}})
if(oldUser) {
throw new ApolloError('A user is already registered with the email' + data.email, 'USER_ALREADY_EXISTS')
}
var encryptedPassword = await bcrypt.hash(data.password, 10)
const newUser = await ctx.prisma.user.create({
data: {
name: data.name,
email: data.email,
password: encryptedPassword
}
})
return {token: jwt.sign(newUser, 'supersecret')}
}
Here is a screen shot of my Preview in my Network...I really don't get it.
export class CreateUserInput {
#Field((type) => String)
name: string
#Field((type) => String)
email: string
#Field((type) => String)
password: string

Modify value inside HOC on NextJS

I've been working on a way to set up authentication and authorization for my NextJS app, so far it was pretty easy but I've hit a wall.
I have a value that lives and is watched on a context, and I have a HOC that I need for my NextJS app to be able to use hooks with GraphQl, the issues is that I don't think I can call the context and use the value from a HOC, since it is simply not allowed.
Is there a way I can dynamically change the value on the HOC so that when the user logs in, I can then update the HOC to have the proper access token?
Some context: the user is first anonymous, whenever he/she logs in, I get an auth state change from Firebase from which I can extract the access token and add it to any future requests. But the point of the hoc is to provide next with full Graphql capabilities, the thing is that I need that hoc go listen for changes on a context state.
This is the Connection Builder:
import {
ApolloClient,
InMemoryCache,
HttpLink,
NormalizedCacheObject,
} from "#apollo/client";
import { WebSocketLink } from "#apollo/client/link/ws";
import { SubscriptionClient } from "subscriptions-transport-ws";
const connectionString = process.env.HASURA_GRAPHQL_API_URL || "";
const createHttpLink = (authState: string, authToken: string) => {
const isIn = authState === "in";
const httpLink = new HttpLink({
uri: `https${connectionString}`,
headers: {
// "X-hasura-admin-secret": `https${connectionString}`,
lang: "en",
"content-type": "application/json",
Authorization: isIn && `Bearer ${authToken}`,
},
});
return httpLink;
};
const createWSLink = (authState: string, authToken: string) => {
const isIn = authState === "in";
return new WebSocketLink(
new SubscriptionClient(`wss${connectionString}`, {
lazy: true,
reconnect: true,
connectionParams: async () => {
return {
headers: {
// "X-hasura-admin-secret": process.env.HASURA_GRAPHQL_ADMIN_SECRET,
lang: "en",
"content-type": "application/json",
Authorization: isIn && `Bearer ${authToken}`,
},
};
},
})
);
};
export default function createApolloClient(
initialState: NormalizedCacheObject,
authState: string,
authToken: string
) {
const ssrMode = typeof window === "undefined";
let link;
if (ssrMode) {
link = createHttpLink(authState, authToken);
} else {
link = createWSLink(authState, authToken);
}
return new ApolloClient({
ssrMode,
link,
cache: new InMemoryCache().restore(initialState),
});
}
This is the context:
import { useState, useEffect, createContext, useContext } from "react";
import { getDatabase, ref, set, onValue } from "firebase/database";
import { useFirebase } from "./use-firebase";
import { useGetUser } from "../hooks/use-get-user";
import { getUser_Users_by_pk } from "../types/generated/getUser";
import { getApp } from "firebase/app";
const FirebaseAuthContext = createContext<FirebaseAuthContextProps>({
authUser: null,
authState: "",
authToken: null,
currentUser: undefined,
loading: true,
login: () => Promise.resolve(undefined),
registerUser: () => Promise.resolve(undefined),
loginWithGoogle: () => Promise.resolve(undefined),
loginWithMicrosoft: () => Promise.resolve(undefined),
});
export const FirebaseAuthContextProvider: React.FC = ({ children }) => {
const [loading, setLoading] = useState<boolean>(true);
const [authUser, setAuthUser] = useState<User | null>(null);
const { data } = useGetUser(authUser?.uid || "");
const [authState, setAuthState] = useState("loading");
const [authToken, setAuthToken] = useState<string | null>(null);
const currentUser = data?.Users_by_pk;
// ...
const authStateChanged = async (user: User | null) => {
if (!user) {
setAuthUser(null);
setLoading(false);
setAuthState("out");
return;
}
const token = await user.getIdToken();
const idTokenResult = await user.getIdTokenResult();
const hasuraClaim = idTokenResult.claims["https://hasura.io/jwt/claims"];
if (hasuraClaim) {
setAuthState("in");
setAuthToken(token);
setAuthUser(user);
} else {
// Check if refresh is required.
const metadataRef = ref(
getDatabase(getApp()),
"metadata/" + user.uid + "/refreshTime"
);
onValue(metadataRef, async (data) => {
if (!data.exists) return;
const token = await user.getIdToken(true);
setAuthState("in");
setAuthUser(user);
setAuthToken(token);
});
}
};
useEffect(() => {
const unsubscribe = getAuth().onAuthStateChanged(authStateChanged);
return () => unsubscribe();
}, []);
const contextValue: FirebaseAuthContextProps = {
authUser,
authState,
authToken,
currentUser,
loading,
login,
registerUser,
loginWithGoogle,
loginWithMicrosoft,
};
return (
<FirebaseAuthContext.Provider value={contextValue}>
{children}
</FirebaseAuthContext.Provider>
);
};
export const useFirebaseAuth = () =>
useContext<FirebaseAuthContextProps>(FirebaseAuthContext);
This is the HOC:
export const withApollo =
({ ssr = true } = {}) =>
(PageComponent: NextComponentType<NextPageContext, any, {}>) => {
const WithApollo = ({
apolloClient,
apolloState,
...pageProps
}: {
apolloClient: ApolloClient<NormalizedCacheObject>;
apolloState: NormalizedCacheObject;
}) => {
let client;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
client = apolloClient;
} else {
// Happens on: next.js csr
// client = initApolloClient(apolloState, undefined);
client = initApolloClient(apolloState);
}
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
const initApolloClient = (initialState: NormalizedCacheObject) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, "", "");
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(initialState, "", "");
}
return globalApolloClient;
};
I fixed it by using this whenever I have an update on the token:
import { setContext } from "#apollo/client/link/context";
const authStateChanged = async (user: User | null) => {
if (!user) {
setAuthUser(null);
setLoading(false);
setAuthState("out");
return;
}
setAuthUser(user);
const token = await user.getIdToken();
const idTokenResult = await user.getIdTokenResult();
const hasuraClaim = idTokenResult.claims["hasura"];
if (hasuraClaim) {
setAuthState("in");
setAuthToken(token);
// THIS IS THE FIX
setContext(() => ({
headers: { Authorization: `Bearer ${token}` },
}));
} else {
// Check if refresh is required.
const metadataRef = ref(
getDatabase(getApp()),
"metadata/" + user.uid + "/refreshTime"
);
onValue(metadataRef, async (data) => {
if (!data.exists) return;
const token = await user.getIdToken(true);
setAuthState("in");
setAuthToken(token);
// THIS IS THE FIX
setContext(() => ({
headers: { Authorization: `Bearer ${token}` },
}));
});
}
};

The bot name is already registered to another bot application

I had previously tried to deploy this bot and had issues. I attempted to delete the resource group and try again from scratch and am getting this error.
Built with: Bot Framework Composer (v1.1.1)
Deploying with: provisionComposer.js
> Deploying Azure services (this could take a while)...
✖
{
"error": {
"code": "InvalidBotData",
"message": "Bot is not valid. Errors: The bot name is already registered to another bot application.. See https://aka.ms/bot-requirements for detailed requirements."
}
}
** Provision failed **
The link in the error message doesn't mention 'bot name' or 'name'.
Does a bot name have to be unique to the subscription, tenant, etc?
Is there a place I need to go to 'un-register' the bot name so that it can be registered to another application? Was deleting the resource group not enough?
Thanks in advance for the assistance.
Best Regards,
Josh
I ran into the same issue. After some modifications to the script I was able to complete the provisioning of resources. Issue was the name used to create some of the resources did not match because of the environment variable appended.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const chalk = require("chalk");
const fs = require("fs-extra");
const msRestNodeAuth = require("#azure/ms-rest-nodeauth");
const argv = require("minimist")(process.argv.slice(2));
const path = require("path");
const rp = require("request-promise");
const { promisify } = require("util");
const { GraphRbacManagementClient } = require("#azure/graph");
const {
ApplicationInsightsManagementClient,
} = require("#azure/arm-appinsights");
const { AzureBotService } = require("#azure/arm-botservice");
const { ResourceManagementClient } = require("#azure/arm-resources");
const readFile = promisify(fs.readFile);
const ora = require("ora");
const logger = (msg) => {
if (msg.status === BotProjectDeployLoggerType.PROVISION_ERROR) {
console.log(chalk.red(msg.message));
} else if (
msg.status === BotProjectDeployLoggerType.PROVISION_ERROR_DETAILS
) {
console.log(chalk.white(msg.message));
} else {
console.log(chalk.green(msg.message));
}
};
const usage = () => {
const options = [
["subscriptionId", "Azure Subscription Id"],
["name", "Project Name"],
["appPassword", "16 character password"],
["environment", "Environment name (Defaults to dev)"],
["location", "Azure Region (Defaults to westus)"],
["appId", "Microsoft App ID (Will create if absent)"],
[
"tenantId",
"ID of your tenant if required (will choose first in list by default)",
],
["createLuisResource", "Create a LUIS resource? Default true"],
[
"createLuisAuthoringResource",
"Create a LUIS authoring resource? Default true",
],
["createCosmosDb", "Create a CosmosDB? Default true"],
["createStorage", "Create a storage account? Default true"],
["createAppInsights", "Create an AppInsights resource? Default true"],
["createQnAResource", "Create a QnA resource? Default true"],
[
"customArmTemplate",
"Path to runtime ARM template. By default it will use an Azure WebApp template. Pass `DeploymentTemplates/function-template-with-preexisting-rg.json` for Azure Functions or your own template for a custom deployment.",
],
];
const instructions = [
``,
chalk.bold(
"Provision Azure resources for use with Bot Framework Composer bots"
),
`* This script will create a new resource group and the necessary Azure resources needed to operate a Bot Framework bot in the cloud.`,
`* Use this to create a publishing profile used in Composer's "Publish" toolbar.`,
``,
chalk.bold(`Basic Usage:`),
chalk.greenBright(`node provisionComposer --subscriptionId=`) +
chalk.yellow("<Azure Subscription Id>") +
chalk.greenBright(" --name=") +
chalk.yellow("<Name for your environment>") +
chalk.greenBright(" --appPassword=") +
chalk.yellow("<16 character password>"),
``,
chalk.bold(`All options:`),
...options.map((option) => {
return (
chalk.greenBright("--" + option[0]) + "\t" + chalk.yellow(option[1])
);
}),
];
console.log(instructions.join("\n"));
};
// check for required parameters
if (Object.keys(argv).length === 0) {
return usage();
}
if (!argv.name || !argv.subscriptionId || !argv.appPassword) {
return usage();
}
// Get required fields from the arguments
const subId = argv.subscriptionId;
const name = argv.name.toString();
const appPassword = argv.appPassword;
// Get optional fields from the arguments
const environment = argv.environment || "dev";
const location = argv.location || "westus";
const appId = argv.appId; // MicrosoftAppId - generated if left blank
// Get option flags
const createLuisResource = argv.createLuisResource == "false" ? false : true;
const createLuisAuthoringResource =
argv.createLuisAuthoringResource == "false" ? false : true;
const createCosmosDb = argv.createCosmosDb == "false" ? false : true;
const createStorage = argv.createStorage == "false" ? false : true;
const createAppInsights = argv.createAppInsights == "false" ? false : true;
const createQnAResource = argv.createQnAResource == "false" ? false : true;
var tenantId = argv.tenantId ? argv.tenantId : "";
const templatePath =
argv.customArmTemplate ||
path.join(
__dirname,
"DeploymentTemplates",
"template-with-preexisting-rg.json"
);
const BotProjectDeployLoggerType = {
// Logger Type for Provision
PROVISION_INFO: "PROVISION_INFO",
PROVISION_ERROR: "PROVISION_ERROR",
PROVISION_WARNING: "PROVISION_WARNING",
PROVISION_SUCCESS: "PROVISION_SUCCESS",
PROVISION_ERROR_DETAILS: "PROVISION_ERROR_DETAILS",
};
/**
* Create a Bot Framework registration
* #param {} graphClient
* #param {*} displayName
* #param {*} appPassword
*/
const createApp = async (graphClient, displayName, appPassword) => {
try {
const createRes = await graphClient.applications.create({
displayName: displayName,
passwordCredentials: [
{
value: appPassword,
startDate: new Date(),
endDate: new Date(
new Date().setFullYear(new Date().getFullYear() + 2)
),
},
],
availableToOtherTenants: true,
replyUrls: ["https://token.botframework.com/.auth/web/redirect"],
});
return createRes;
} catch (err) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: err.body.message,
});
return false;
}
};
/**
* Create an Azure resources group
* #param {} client
* #param {*} location
* #param {*} resourceGroupName
*/
const createResourceGroup = async (client, location, resourceGroupName) => {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Creating resource group ...`,
});
const param = {
location: location,
};
return await client.resourceGroups.createOrUpdate(resourceGroupName, param);
};
/**
* Format parameters
* #param {} scope
*/
const pack = (scope) => {
return {
value: scope,
};
};
const unpackObject = (output) => {
const unpacked = {};
for (const key in output) {
const objValue = output[key];
if (objValue.value) {
unpacked[key] = objValue.value;
}
}
return unpacked;
};
/**
* For more information about this api, please refer to this doc: https://learn.microsoft.com/en-us/rest/api/resources/Tenants/List
* #param {*} accessToken
*/
const getTenantId = async (accessToken) => {
if (!accessToken) {
throw new Error(
"Error: Missing access token. Please provide a non-expired Azure access token. Tokens can be obtained by running az account get-access-token"
);
}
if (!subId) {
throw new Error(
`Error: Missing subscription Id. Please provide a valid Azure subscription id.`
);
}
try {
const tenantUrl = `https://management.azure.com/subscriptions/${subId}?api-version=2020-01-01`;
const options = {
headers: { Authorization: `Bearer ${accessToken}` },
};
const response = await rp.get(tenantUrl, options);
const jsonRes = JSON.parse(response);
if (jsonRes.tenantId === undefined) {
throw new Error(`No tenants found in the account.`);
}
return jsonRes.tenantId;
} catch (err) {
throw new Error(`Get Tenant Id Failed, details: ${getErrorMesssage(err)}`);
}
};
const getDeploymentTemplateParam = (
appId,
appPwd,
location,
name,
shouldCreateAuthoringResource,
shouldCreateLuisResource,
shouldCreateQnAResource,
useAppInsights,
useCosmosDb,
useStorage
) => {
return {
appId: pack(appId),
appSecret: pack(appPwd),
appServicePlanLocation: pack(location),
botId: pack(name),
shouldCreateAuthoringResource: pack(shouldCreateAuthoringResource),
shouldCreateLuisResource: pack(shouldCreateLuisResource),
shouldCreateQnAResource: pack(shouldCreateQnAResource),
useAppInsights: pack(useAppInsights),
useCosmosDb: pack(useCosmosDb),
useStorage: pack(useStorage),
};
};
/**
* Validate the deployment using the Azure API
*/
const validateDeployment = async (
client,
resourceGroupName,
deployName,
templateParam
) => {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: "> Validating Azure deployment ...",
});
const templateFile = await readFile(templatePath, { encoding: "utf-8" });
const deployParam = {
properties: {
template: JSON.parse(templateFile),
parameters: templateParam,
mode: "Incremental",
},
};
return await client.deployments.validate(
resourceGroupName,
deployName,
deployParam
);
};
/**
* Using an ARM template, provision a bunch of resources
*/
const createDeployment = async (
client,
resourceGroupName,
deployName,
templateParam
) => {
const templateFile = await readFile(templatePath, { encoding: "utf-8" });
const deployParam = {
properties: {
template: JSON.parse(templateFile),
parameters: templateParam,
mode: "Incremental",
},
};
return await client.deployments.createOrUpdate(
resourceGroupName,
deployName,
deployParam
);
};
/**
* Format the results into the expected shape
*/
const updateDeploymentJsonFile = async (
client,
resourceGroupName,
deployName,
appId,
appPwd
) => {
const outputs = await client.deployments.get(resourceGroupName, deployName);
if (outputs && outputs.properties && outputs.properties.outputs) {
const outputResult = outputs.properties.outputs;
const applicationResult = {
MicrosoftAppId: appId,
MicrosoftAppPassword: appPwd,
};
const outputObj = unpackObject(outputResult);
if (!createAppInsights) {
delete outputObj.applicationInsights;
}
if (!createCosmosDb) {
delete outputObj.cosmosDb;
}
if (!createLuisAuthoringResource && !createLuisResource) {
delete outputObj.luis;
}
if (!createStorage) {
delete outputObj.blobStorage;
}
const result = {};
Object.assign(result, outputObj, applicationResult);
return result;
} else {
return null;
}
};
const provisionFailed = (msg) => {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: chalk.bold("** Provision failed **"),
});
};
const getErrorMesssage = (err) => {
if (err.body) {
if (err.body.error) {
if (err.body.error.details) {
const details = err.body.error.details;
let errMsg = "";
for (let detail of details) {
errMsg += detail.message;
}
return errMsg;
} else {
return err.body.error.message;
}
} else {
return JSON.stringify(err.body, null, 2);
}
} else {
return JSON.stringify(err, null, 2);
}
};
/**
* Provision a set of Azure resources for use with a bot
*/
const create = async (
creds,
subId,
name,
location,
environment,
appId,
appPassword,
createLuisResource = true,
createLuisAuthoringResource = true,
createQnAResource = true,
createCosmosDb = true,
createStorage = true,
createAppInsights = true
) => {
// If tenantId is empty string, get tenanId from API
if (!tenantId) {
const token = await creds.getToken();
const accessToken = token.accessToken;
// the returned access token will almost surely have a tenantId.
// use this as the default if one isn't specified.
if (token.tenantId) {
tenantId = token.tenantId;
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Using Tenant ID: ${tenantId}`,
});
} else {
tenantId = await getTenantId(accessToken);
}
}
const graphCreds = new msRestNodeAuth.DeviceTokenCredentials(
creds.clientId,
tenantId,
creds.username,
"graph",
creds.environment,
creds.tokenCache
);
const graphClient = new GraphRbacManagementClient(graphCreds, tenantId, {
baseUri: "https://graph.windows.net",
});
// If the appId is not specified, create one
if (!appId) {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: "> Creating App Registration ...",
});
// create the app registration
const appCreated = await createApp(
graphClient,
`${name}-${environment}`,
appPassword
);
if (appCreated === false) {
return provisionFailed();
}
// use the newly created app
appId = appCreated.appId;
}
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Create App Id Success! ID: ${appId}`,
});
const resourceGroupName = `${name}-${environment}`;
const deployName = `${name}-${environment}-deploy`;
// timestamp will be used as deployment name
const timeStamp = new Date().getTime().toString();
const client = new ResourceManagementClient(creds, subId);
// Create a resource group to contain the new resources
try {
const rpres = await createResourceGroup(
client,
location,
resourceGroupName
);
} catch (err) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: getErrorMesssage(err),
});
return provisionFailed();
}
// Caste the parameters into the right format
const deploymentTemplateParam = getDeploymentTemplateParam(
appId,
appPassword,
location,
`${name}-${environment}`,
createLuisAuthoringResource,
createQnAResource,
createLuisResource,
createAppInsights,
createCosmosDb,
createStorage
);
// Validate the deployment using the Azure API
const validation = await validateDeployment(
client,
resourceGroupName,
deployName,
deploymentTemplateParam
);
// Handle validation errors
if (validation.error) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Error: ${validation.error.message}`,
});
if (validation.error.details) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR_DETAILS,
message: JSON.stringify(validation.error.details, null, 2),
});
}
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `+ To delete this resource group, run 'az group delete -g ${resourceGroupName} --no-wait'`,
});
return provisionFailed();
}
// Create the entire stack of resources inside the new resource group
// this is controlled by an ARM template identified in templatePath
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Deploying Azure services (this could take a while)...`,
});
const spinner = ora().start();
try {
const deployment = await createDeployment(
client,
resourceGroupName,
deployName,
deploymentTemplateParam
);
// Handle errors
if (deployment._response.status != 200) {
spinner.fail();
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Template is not valid with provided parameters. Review the log for more information.`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Error: ${validation.error}`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `+ To delete this resource group, run 'az group delete -g ${resourceGroupName} --no-wait'`,
});
return provisionFailed();
}
} catch (err) {
spinner.fail();
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: getErrorMesssage(err),
});
return provisionFailed();
}
// If application insights created, update the application insights settings in azure bot service
if (createAppInsights) {
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Linking Application Insights settings to Bot Service ...`,
});
const appinsightsClient = new ApplicationInsightsManagementClient(
creds,
subId
);
const appComponents = await appinsightsClient.components.get(
resourceGroupName,
resourceGroupName
);
const appinsightsId = appComponents.appId;
const appinsightsInstrumentationKey = appComponents.instrumentationKey;
const apiKeyOptions = {
name: `${resourceGroupName}-provision-${timeStamp}`,
linkedReadProperties: [
`/subscriptions/${subId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/components/${resourceGroupName}/api`,
`/subscriptions/${subId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/components/${resourceGroupName}/agentconfig`,
],
linkedWriteProperties: [
`/subscriptions/${subId}/resourceGroups/${resourceGroupName}/providers/microsoft.insights/components/${resourceGroupName}/annotations`,
],
};
const appinsightsApiKeyResponse = await appinsightsClient.aPIKeys.create(
resourceGroupName,
resourceGroupName,
apiKeyOptions
);
const appinsightsApiKey = appinsightsApiKeyResponse.apiKey;
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> AppInsights AppId: ${appinsightsId} ...`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> AppInsights InstrumentationKey: ${appinsightsInstrumentationKey} ...`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> AppInsights ApiKey: ${appinsightsApiKey} ...`,
});
if (appinsightsId && appinsightsInstrumentationKey && appinsightsApiKey) {
const botServiceClient = new AzureBotService(creds, subId);
const botCreated = await botServiceClient.bots.get(
resourceGroupName,
`${name}-${environment}`
);
if (botCreated.properties) {
botCreated.properties.developerAppInsightKey = appinsightsInstrumentationKey;
botCreated.properties.developerAppInsightsApiKey = appinsightsApiKey;
botCreated.properties.developerAppInsightsApplicationId = appinsightsId;
const botUpdateResult = await botServiceClient.bots.update(
resourceGroupName,
`${name}-${environment}`,
botCreated
);
if (botUpdateResult._response.status != 200) {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Something went wrong while trying to link Application Insights settings to Bot Service Result: ${JSON.stringify(
botUpdateResult
)}`,
});
throw new Error(`Linking Application Insights Failed.`);
}
logger({
status: BotProjectDeployLoggerType.PROVISION_INFO,
message: `> Linking Application Insights settings to Bot Service Success!`,
});
} else {
logger({
status: BotProjectDeployLoggerType.PROVISION_WARNING,
message: `! The Bot doesn't have a keys properties to update.`,
});
}
}
}
spinner.succeed("Success!");
// Validate that everything was successfully created.
// Then, update the settings file with information about the new resources
const updateResult = await updateDeploymentJsonFile(
client,
resourceGroupName,
deployName,
appId,
appPassword
);
// Handle errors
if (!updateResult) {
const operations = await client.deploymentOperations.list(
resourceGroupName,
deployName
);
if (operations) {
const failedOperations = operations.filter(
(value) =>
value &&
value.properties &&
value.properties.statusMessage.error !== null
);
if (failedOperations) {
failedOperations.forEach((operation) => {
switch (
operation &&
operation.properties &&
operation.properties.statusMessage.error.code &&
operation.properties.targetResource
) {
case "MissingRegistrationForLocation":
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Deployment failed for resource of type ${operation.properties.targetResource.resourceType}. This resource is not avaliable in the location provided.`,
});
break;
default:
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Deployment failed for resource of type ${operation.properties.targetResource.resourceType}.`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Code: ${operation.properties.statusMessage.error.code}.`,
});
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Message: ${operation.properties.statusMessage.error.message}.`,
});
break;
}
});
}
} else {
logger({
status: BotProjectDeployLoggerType.PROVISION_ERROR,
message: `! Deployment failed. Please refer to the log file for more information.`,
});
}
}
return updateResult;
};
console.log(chalk.bold("Login to Azure:"));
msRestNodeAuth
.interactiveLogin({ domain: tenantId })
.then(async (creds) => {
const createResult = await create(
creds,
subId,
name,
location,
environment,
appId,
appPassword,
createLuisResource,
createLuisAuthoringResource,
createQnAResource,
createCosmosDb,
createStorage,
createAppInsights
);
if (createResult) {
console.log("");
console.log(
chalk.bold(
`Your Azure hosting environment has been created! Copy paste the following configuration into a new profile in Composer's Publishing tab.`
)
);
console.log("");
const token = await creds.getToken();
const profile = {
accessToken: token.accessToken,
name: `${name}-${environment}`,
environment: environment,
hostname: `${name}-${environment}`,
luisResource: `${name}-${environment}-luis`,
settings: createResult,
};
console.log(chalk.white(JSON.stringify(profile, null, 2)));
console.log("");
}
})
.catch((err) => {
console.error(err);
});
Hope it works for you as well!

koa, sessions, redis: how to make it work?

I am trying to implement Firebase authentication with server-side sessions using koa, koa-session, koa-redis.
I just can't grasp it. When reading the koa-session readme, this is particularly cryptic to me (link):
You can store the session content in external stores (Redis, MongoDB or other DBs) by passing options.store with three methods (these need to be async functions):
get(key, maxAge, { rolling }): get session object by key
set(key, sess, maxAge, { rolling, changed }): set session object for key, with a maxAge (in ms)
destroy(key): destroy session for key
After asking around, I did this:
// middleware/installSession.js
const session = require('koa-session');
const RedisStore = require('koa-redis');
const ONE_DAY = 1000 * 60 * 60 * 24;
module.exports = function installSession(app) {
app.keys = [process.env.SECRET];
app.use(session({
store: new RedisStore({
url: process.env.REDIS_URL,
key: process.env.REDIS_STORE_KEY,
async get(key) {
const res = await redis.get(key);
if (!res) return null;
return JSON.parse(res);
},
async set(key, value, maxAge) {
maxAge = typeof maxAge === 'number' ? maxAge : ONE_DAY;
value = JSON.stringify(value);
await redis.set(key, value, 'PX', maxAge);
},
async destroy(key) {
await redis.del(key);
},
})
}, app));
};
Then in my main server.js file:
// server.js
...
const middleware = require('./middleware');
const app = new Koa();
const server = http.createServer(app.callback());
// session middleware
middleware.installSession(app);
// other middleware, which also get app as a parameter
middleware.installFirebaseAuth(app);
...
const PORT = parseInt(process.env.PORT, 10) || 3000;
server.listen(PORT);
console.log(`Listening on port ${PORT}`);
But then how do I access the session and its methods from inside other middlewares? Like in the installFirebaseAuth middleware, I want to finally get/set session values:
// installFirebaseAuth.js
...
module.exports = function installFirebaseAuth(app) {
...
const verifyAccessToken = async (ctx, next) => {
...
// trying to access the session, none work
console.log('ctx.session', ctx.session);
console.log('ctx.session.get():'
ctx.session.get(process.env.REDIS_STORE_KEY));
console.log('ctx.req.session', ctx.req.session);
const redisValue = await ctx.req.session.get(process.env.REDIS_STORE_KEY);
...
}
}
ctx.session returns {}
ctx.session.get() returns ctx.session.get is not a function
ctx.req.session returns undefined
Any clues?
Thanks!!
It works in my case, hope it helps you
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
const static = require('koa-static')
const session = require('koa-session')
// const ioredis = require('ioredis')
// const redisStore = new ioredis()
const redisStore = require('koa-redis')
const bodyparser = require('koa-bodyparser')
app.use(static('.'))
app.use(bodyparser())
app.keys = ['ohkeydoekey']
app.use(session({
key: 'yokiijay:sess',
maxAge: 1000*20,
store: redisStore()
}, app))
app.use(router.routes(), router.allowedMethods())
router.post('/login', async ctx=>{
const {username} = ctx.request.body
if(username == 'yokiijay'){
ctx.session.user = username
const count = ctx.session.count || 0
ctx.session.code = count
ctx.body = `wellcome ${username} logged in`
}else {
ctx.body = `sorry, you can't login`
}
})
router.get('/iflogin', async ctx=>{
if(ctx.session.user){
ctx.body = ctx.session
}else {
ctx.body = 'you need login'
}
})
app.listen(3000, ()=>{
console.log( 'app running' )
})

Resources