2
0

auth.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { Instance } from "@/project/instance"
  2. import { Plugin } from "../plugin"
  3. import { map, filter, pipe, fromEntries, mapValues } from "remeda"
  4. import z from "zod"
  5. import { fn } from "@/util/fn"
  6. import type { AuthOuathResult, Hooks } from "@opencode-ai/plugin"
  7. import { NamedError } from "@opencode-ai/util/error"
  8. import { Auth } from "@/auth"
  9. export namespace ProviderAuth {
  10. const state = Instance.state(async () => {
  11. const methods = pipe(
  12. await Plugin.list(),
  13. filter((x) => x.auth?.provider !== undefined),
  14. map((x) => [x.auth!.provider, x.auth!] as const),
  15. fromEntries(),
  16. )
  17. return { methods, pending: {} as Record<string, AuthOuathResult> }
  18. })
  19. export const Method = z
  20. .object({
  21. type: z.union([z.literal("oauth"), z.literal("api")]),
  22. label: z.string(),
  23. })
  24. .meta({
  25. ref: "ProviderAuthMethod",
  26. })
  27. export type Method = z.infer<typeof Method>
  28. export async function methods() {
  29. const s = await state().then((x) => x.methods)
  30. return mapValues(s, (x) =>
  31. x.methods.map(
  32. (y): Method => ({
  33. type: y.type,
  34. label: y.label,
  35. }),
  36. ),
  37. )
  38. }
  39. export const Authorization = z
  40. .object({
  41. url: z.string(),
  42. method: z.union([z.literal("auto"), z.literal("code")]),
  43. instructions: z.string(),
  44. })
  45. .meta({
  46. ref: "ProviderAuthAuthorization",
  47. })
  48. export type Authorization = z.infer<typeof Authorization>
  49. export const authorize = fn(
  50. z.object({
  51. providerID: z.string(),
  52. method: z.number(),
  53. }),
  54. async (input): Promise<Authorization | undefined> => {
  55. const auth = await state().then((s) => s.methods[input.providerID])
  56. const method = auth.methods[input.method]
  57. if (method.type === "oauth") {
  58. const result = await method.authorize()
  59. await state().then((s) => (s.pending[input.providerID] = result))
  60. return {
  61. url: result.url,
  62. method: result.method,
  63. instructions: result.instructions,
  64. }
  65. }
  66. },
  67. )
  68. export const callback = fn(
  69. z.object({
  70. providerID: z.string(),
  71. method: z.number(),
  72. code: z.string().optional(),
  73. }),
  74. async (input) => {
  75. const match = await state().then((s) => s.pending[input.providerID])
  76. if (!match) throw new OauthMissing({ providerID: input.providerID })
  77. let result
  78. if (match.method === "code") {
  79. if (!input.code) throw new OauthCodeMissing({ providerID: input.providerID })
  80. result = await match.callback(input.code)
  81. }
  82. if (match.method === "auto") {
  83. result = await match.callback()
  84. }
  85. if (result?.type === "success") {
  86. if ("key" in result) {
  87. await Auth.set(input.providerID, {
  88. type: "api",
  89. key: result.key,
  90. })
  91. }
  92. if ("refresh" in result) {
  93. const info: Auth.Info = {
  94. type: "oauth",
  95. access: result.access,
  96. refresh: result.refresh,
  97. expires: result.expires,
  98. }
  99. if (result.accountId) {
  100. info.accountId = result.accountId
  101. }
  102. await Auth.set(input.providerID, info)
  103. }
  104. return
  105. }
  106. throw new OauthCallbackFailed({})
  107. },
  108. )
  109. export const api = fn(
  110. z.object({
  111. providerID: z.string(),
  112. key: z.string(),
  113. }),
  114. async (input) => {
  115. await Auth.set(input.providerID, {
  116. type: "api",
  117. key: input.key,
  118. })
  119. },
  120. )
  121. export const OauthMissing = NamedError.create(
  122. "ProviderAuthOauthMissing",
  123. z.object({
  124. providerID: z.string(),
  125. }),
  126. )
  127. export const OauthCodeMissing = NamedError.create(
  128. "ProviderAuthOauthCodeMissing",
  129. z.object({
  130. providerID: z.string(),
  131. }),
  132. )
  133. export const OauthCallbackFailed = NamedError.create("ProviderAuthOauthCallbackFailed", z.object({}))
  134. }