Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to add custom netlify functions #1249

Closed
loweisz opened this issue Apr 27, 2021 · 27 comments · Fixed by #2113
Closed

Is it possible to add custom netlify functions #1249

loweisz opened this issue Apr 27, 2021 · 27 comments · Fixed by #2113

Comments

@loweisz
Copy link

loweisz commented Apr 27, 2021

Hey there 👋

I'm trying to deploy my svelte kit app to netlify, which seems to be working perfectly, when using the adapter.

However, I also wanted to deploy a custom function of mine and I noticed that this function is never deployed to netlify. Only the function called render.

I also noticed that when I add my custom function to the specified functions directory and then run npm run build, my custom function is removed from the functions folder and only the render function is still there.

I'm wondering am I missing something here or is it currently not possible to add a custom function to netlify when using the adapter ?

Thanks in advance 🙇‍♂️

@benmccann
Copy link
Member

Can you just create a SvelteKit endpoint?

This may also be somewhat a duplicate of #930 where existing Netlify config is overwritten

@asgerb

This comment has been minimized.

@benmccann

This comment has been minimized.

@loweisz
Copy link
Author

loweisz commented May 18, 2021

@benmccann Sorry for the late response.

Yes with SvelteKit endpoints it works perfectly, thanks for the tip.
However, I would still say it might be a confusing behavior when trying to use netlify functions directly.
But I could solve my use case without 👍

@ClaytonFarr
Copy link

ClaytonFarr commented May 23, 2021

@benmccann I think this is still a real issue and need.

There is unique Netlify functionality that doesn't seem accessible via SvelteKit endpoints yet.

Two examples I'm currently encountering are -

  1. Not able to take advantage of specially named functions that trigger on events (https://docs.netlify.com/functions/trigger-on-events)
  2. Not able to access user context information from Netlify authentication product 'Identity'; this is needed to be able to perform admin actions (https://docs.netlify.com/functions/functions-and-identity)

I'm not sure if / when it'll make sense for SvelteKit to afford this ability via endpoints (esp if #2 is unique to Netlify only).

But, being able to create and include additional serverless functions in the build somehow (e.g. copying files from an aliased directory into the 'build/functions' directory) should make this work.

Without it, I'm finding myself running in circles trying to implement a full auth solution using Netlify.

If there's a way to work around this in the short, that'd be great to know.

Thanks.

@benmccann
Copy link
Member

Interesting. I wonder if perhaps the Netlify adapter should copy context.clientContext into SvelteKit's locals

@ClaytonFarr
Copy link

That'd a big help for issue #2 above.

Being able to also add specially named function files would be very nice too. I'm not sure if Netlify can catch these as function names bundled in SKs 'render' function. It doesn't seem like it.

@ClaytonFarr
Copy link

Looking forward to this hopefully being achievable natively in the Netlify adapter.

For others who stumble into this need and issue in the meantime, this workaround may be helpful -

https://discord.com/channels/457912077277855764/819723698415599626/846467065196052510

@splatte
Copy link

splatte commented May 29, 2021

My usecase is similar to @ClaytonFarr's as I want to prevent protected admin pages from rendering unless the user is logged in. I am using patch-package to make this small change to the netlify-adapter https://gist.github.com/splatte/8d57ba18d5f0643119e242beb424a3c3 which lets me access user context information in SvelteKit's handle function:

  netlifyContext: {
    custom: {
      netlify: 'eyJpZGVudGl0eSI6eyJ1cmwiOiJxxxxxxxxxxxxxxxxxIQWlPakUyTWpJeU5UvY29'
    },
    identity: {
      url: 'https://abcdfdfdfdfd.netlify.app/.netlify/identity',
      token: 'eyJhbGciOiJIxxxxxxxxxxxxxxxkpQqdV0bDJVgq562Ac'
    },
    user: { /* only populated when Authorization: Bearer <token> is present in request */ }
  },

However, https://docs.netlify.com/functions/functions-and-identity/ states that "The user object is present if the function request has an Authorization: Bearer header with a valid JWT from the Identity instance. In this case the object will contain the decoded claims." so the header does not seem to be present when accessing the page via the browser. The browser sends the token via the nf_jwt cookie which doesn't get decoded by netlify automatically.

I hope I am on the right track here, but how do I get netlify to decode the claims automatically when the user accesses a protected page like /admin/secret-page?

@ClaytonFarr
Copy link

ClaytonFarr commented May 29, 2021

@splatte Nice. I wasn't aware of using an approach like patch-package in the interim; I'll have to try that.

Regarding passing the token as an authorization header, you can parse your cookie in the hooks.js to use it in your function call.

Hooks.js will receive all cookies automatically on each request. You can then parse the JWT and attach it to the request.locals object (including httpOnly cookies) to have it available to the request, to or attach any of the decoded JWT claims to the session.

Here is some example code from a current project -
https://gist.github.com/ClaytonFarr/d4555f396179b0375652285abe43d37e

  1. within hooks.js you'll see the parsing of the cookie token and attaching it to request.locals.token
  2. within updateEmail.js you'll see this request.locals.token value being grabbed and passed to a helper method for Netlify Identity (I ended up recreating the GoTrue-JS methods in the auth-api.js file to be able to access an Identity instance directly in serverless functions.)
  3. within the helper method in auth-api.js you'll see this token being attached as an authorization bearer token in the final call.

For cases where you also need an identity admin token from clientContext, you'd either leverage your patch to get this data out of a normal SvelteKit endpoint (if I understand it correctly) – or create a custom serverless function and call this function as a mid-step to get the admin token before hitting the final Netlify Identity endpoint.

@splatte
Copy link

splatte commented May 31, 2021

awesome, thanks a lot for this great input @ClaytonFarr! I have a lot to go through :)

@swyxio
Copy link
Contributor

swyxio commented Jun 13, 2021

@ClaytonFarr i might be able to help... are you able to provide a small sample repo with what you're trying to do?

@ClaytonFarr
Copy link

Thanks @sw-yx, that's incredibly kind.

Currently, I'm manually creating functions that either require additional clientContext (e.g. for a Netlify Identity admin token) or that have to be specially named to trigger on events (e.g. identity-signup) and automatically copying these post-build into the functions directory.

Are you thinking there may be a better way to handle this? If so, I can definitely pull together some examples. Thanks again.

@swyxio
Copy link
Contributor

swyxio commented Jun 14, 2021

@ClaytonFarr yes i do think that there may be a way to handle this, but also the netlify adapter was probably not created with this usecase in mind so we may want to fix this uptstream. examples would help motivate the discussion.

fwiw i used to work at netlify and helped make netlify dev so thats why im volunteering since my background can probably help here

@ClaytonFarr
Copy link

ClaytonFarr commented Jun 14, 2021

@sw-yx Great, thanks. I should be able to pull together a smaller example that helps illustrates this within the next few days.

@ClaytonFarr
Copy link

@sw-yx Sorry for the delay in sharing an example. Was smacked with work for a moment. I'm tying up a few pieces on a project that should be a good example of key use cases that run into this problem. I hope to have that done this week.

@ClaytonFarr
Copy link

ClaytonFarr commented Jun 28, 2021

@sw-yx Here's a repo of an example project that exemplifies the limitations and workarounds I'm currently seeing / implementing -

repo
running demo

[Update: realized I was missing a portion of the setup & instructions in the repo / demo above that was preventing new signups from confirming. Should be fixed now.]

This has several moving parts and integrations (Netlify Identity for authentication, Fauna for user data, Stripe for subscription plans and management, Tailwind). The most relevant parts for this are the additional serverless functions that need to be created outside of normal SK endpoints -

  1. to access data in context.clientContext (e.g. identity tokens) -
  • /src/additional_functions/delete-identity.js – a function called explicitly by SK endpoints
  • /src/additional_functions/handle-subscription-change.js – a webhook triggered by an external event (Stripe subscription update)
  1. to create specially named functions that trigger on events (e.g. post user signup) –
  • /src/additional_functions/identity-signup.js - a function called automatically when a new user completes sign-up process

These 'additional_functions' are currently copied post build into the final functions directory via package.json script.

Within the demo you can create / manage / delete users to experience functionality.

Hope this helps. Thanks again Shawn

@swyxio
Copy link
Contributor

swyxio commented Jun 28, 2021

oh wow awesome :) :) :) so if this is a working example, what is the question - you want to figure out how to drop the postbuild script, thats the only pain point?

@ClaytonFarr
Copy link

It is working, but it feels like it’s doing so in spite of SvelteKit rather than because of it ;)

Ideally, one could create these special functions (that I’ve done in the ‘additional-functions’ directory) as standard SvelteKit endpoints where -

  1. SK included the clientContext data in the function’s ‘context’ object (as Ben noted above), and
  2. The Netlify Adapter provided a mechanism to either choose to build all endpoints as discrete serverless functions with their names intact (instead of within a single ‘render’ function), or to selectively omit specific endpoints and build those separately. (Also curious to see how separate functions v. a single function would affect performance and lambda usage/billing.)

This would allow you to still build everything ‘in-house’ within SK and have the adapter be able to account for the functionality opportunities and expectations of a specific platform, like Netlify.

@swyxio
Copy link
Contributor

swyxio commented Jun 29, 2021

i'm no expert but i think 2) is not possible with SvelteKit's current design - it is too predicated on a single "fat function". 1) can be done with some work i think but will need more exposed APIs upstream

@ClaytonFarr
Copy link

ClaytonFarr commented Jun 29, 2021

I haven't dug into other adapters yet but had assumed it was the Netlify Adapter that's making the call to bundled all SK endpoints into a single serverless function called 'render' – and that other adapters, like for node, would likely keep the SK endpoints as discrete elements. Do you mean that's not the case?

@swyxio
Copy link
Contributor

swyxio commented Jun 30, 2021

i havent dug into the others either but yeah thats how i think it works. @benmccann can ratify/correct me. i know this isn't exactly how nextjs does it.

@reedshea
Copy link

reedshea commented Jul 3, 2021

As I work on some prototypes to test out and evaluate SvelteKit + Netlify + Fauna + Stripe + etc., it's so helpful to have this type of discussion available to read and try to learn from. Thank you, @sw-yx and @ClaytonFarr for "working with the garage door up".

A good deal of info seems to be passed along from the Netlify Function, such as most / all of the event headers. That's helpful for things like protecting endpoints - I'm looking at an allow-list of IPs for data ingestion, and it appears that event.headers?.['x-nf-client-connection-ip'] seems to work pretty well for that need (for Netlify specifically). Having the context seems pretty necessary for being able to take advantage of Netlify Identity.

@ClaytonFarr
Copy link

ClaytonFarr commented Jul 4, 2021

Thanks @reedshea, completely agree. I’ve since added more detail and notes to that example repo. I hope that helps -

https://github.com/ClaytonFarr/sveltekit-netlify-stripe-fauna-example

@swyxio
Copy link
Contributor

swyxio commented Jul 5, 2021

yeah. we'll need in principle approval for this #1779 before i tackle it.

the other thing we can try instead is to NOT handle this inside the netlify adapter, and to modify netlify dev instead to proxy sveltekit endpoints properly. @ascorbic and @jlengstorf seem to be the people to ask here. related issue #1286

@ascorbic
Copy link
Contributor

ascorbic commented Aug 5, 2021

I think a better approach to this is to allow users to create their own Netlify functions as normal, and don't wipe them out with the SvelteKit ones. The easiest way to do that is to not set the functions dir to .sveltekit/netlify/functions, but rather write the entrypoint function to .netlify/functions-internal. That is .gitignored and won't interfere with any user functions.

Regarding access to Netlify context from in SvelteKit, an approach similar to the one suggested by @splatte could work. For Next.js and Gatsby we attach an extra netlifyFunctionParams object to the request that contains the full event and context objects. We could do similar if that helps here.

@swyxio
Copy link
Contributor

swyxio commented Aug 9, 2021

after some more thinking I think @ascorbic is correct. I have withdrawn #1779 and think it is better to go with the netlify dev approach

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants